dsh-provider-proxy 0.5.1 → 0.6.1

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/lib/index.js CHANGED
@@ -1167,7 +1167,64 @@ function normalizeRoute(provider, raw, options) {
1167
1167
  return { ...value, host, username: value.username.trim(), onlyHosts, bypassHosts, testTarget };
1168
1168
  }
1169
1169
 
1170
+ // src/host-rules.ts
1171
+ function splitHostRules(raw) {
1172
+ return raw.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1173
+ }
1174
+ function normalizeHostRule(raw) {
1175
+ return raw.trim().replace(/^\*?\./, "").toLowerCase();
1176
+ }
1177
+ function parseHostList(raw) {
1178
+ return splitHostRules(raw).map(normalizeHostRule).filter((entry) => entry.length > 0);
1179
+ }
1180
+ function hostMatches(host, rules) {
1181
+ for (const rule of rules) {
1182
+ if (rule === "*") return true;
1183
+ if (host === rule || host.endsWith(`.${rule}`)) return true;
1184
+ }
1185
+ return false;
1186
+ }
1187
+ function isBracketedIpv6(token) {
1188
+ return /^\[[0-9a-f:.]+\]$/i.test(token);
1189
+ }
1190
+ function hostRuleProblem(raw) {
1191
+ const token = raw.trim();
1192
+ if (token.length === 0) return void 0;
1193
+ if (token === "*." || normalizeHostRule(token).length === 0) return "\u7A7A\u901A\u914D\u7B26";
1194
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token)) return "\u5E26\u534F\u8BAE\u524D\u7F00";
1195
+ if (token.includes("/")) return "\u5E26\u8DEF\u5F84";
1196
+ if (token.includes(":") && !isBracketedIpv6(token)) return "\u5E26\u7AEF\u53E3\u6216\u975E\u6CD5\u5192\u53F7";
1197
+ const withoutLeadingWildcard = token.replace(/^\*\./, "");
1198
+ if (withoutLeadingWildcard.includes("*") && token !== "*") return "\u6742\u6563\u901A\u914D\u7B26";
1199
+ return void 0;
1200
+ }
1201
+ function deadRuleEntries(raw) {
1202
+ return splitHostRules(raw).filter((entry) => hostRuleProblem(entry) !== void 0);
1203
+ }
1204
+
1170
1205
  // src/proxy-table.ts
1206
+ function normalizedRuleSet(raw) {
1207
+ const rules = (raw ?? "").split(/[,\s]+/).map(normalizeHostRule).filter((entry) => entry.length > 0);
1208
+ return [...new Set(rules)].sort();
1209
+ }
1210
+ function routeFingerprint(input) {
1211
+ return JSON.stringify([
1212
+ input.protocol ?? "http",
1213
+ String(input.host ?? "").trim(),
1214
+ input.port ?? 0,
1215
+ String(input.username ?? "").trim(),
1216
+ input.password ?? "",
1217
+ normalizedRuleSet(input.onlyHosts),
1218
+ normalizedRuleSet(input.bypassHosts),
1219
+ input.localDirect !== false
1220
+ ]);
1221
+ }
1222
+ function routeStateDecision(previous, next) {
1223
+ if (previous === void 0) return { token: "new", clearLastUse: false };
1224
+ if (previous.fingerprint !== next.fingerprint) return { token: "new", clearLastUse: true };
1225
+ if (previous.enabled !== next.enabled) return { token: "new", clearLastUse: false };
1226
+ return { token: "keep", clearLastUse: false };
1227
+ }
1171
1228
  function proxyUrl(route) {
1172
1229
  const credentials = route.username.length === 0 ? "" : route.password.length > 0 && route.protocol !== "socks5" ? `${encodeURIComponent(route.username)}:${encodeURIComponent(route.password)}@` : `${encodeURIComponent(route.username)}@`;
1173
1230
  const host = route.host.includes(":") && !route.host.startsWith("[") ? `[${route.host}]` : route.host;
@@ -1187,16 +1244,6 @@ function createDispatcher(route, url, password) {
1187
1244
  }
1188
1245
  return new ProxyAgent({ uri: url });
1189
1246
  }
1190
- function parseHostList(raw) {
1191
- return raw.split(/[,\s]+/).map((entry) => entry.trim().replace(/^\*?\./, "")).filter((entry) => entry.length > 0);
1192
- }
1193
- function hostMatches(host, rules) {
1194
- for (const rule of rules) {
1195
- if (rule === "*") return true;
1196
- if (host === rule || host.endsWith(`.${rule}`)) return true;
1197
- }
1198
- return false;
1199
- }
1200
1247
  function isLocalHost(host) {
1201
1248
  const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
1202
1249
  if (bare === "localhost" || bare.endsWith(".localhost")) return true;
@@ -1347,12 +1394,16 @@ function apply(ctx, config) {
1347
1394
  let closed = false;
1348
1395
  let webAvailable = false;
1349
1396
  const hits = /* @__PURE__ */ new Map();
1397
+ const routeStates = /* @__PURE__ */ new Map();
1398
+ let nextRouteToken = 1;
1350
1399
  const lastUse = /* @__PURE__ */ new Map();
1351
- const bump = (provider) => {
1400
+ const bump = (provider, token) => {
1352
1401
  hits.set(provider, (hits.get(provider) ?? 0) + 1);
1402
+ if (routeStates.get(provider)?.token !== token) return;
1353
1403
  lastUse.set(provider, { ...lastUse.get(provider), startedAt: Date.now() });
1354
1404
  };
1355
- const settle = (provider) => (ok, detail) => {
1405
+ const settle = (provider, token) => (ok, detail) => {
1406
+ if (routeStates.get(provider)?.token !== token) return;
1356
1407
  const previous = lastUse.get(provider);
1357
1408
  lastUse.set(provider, {
1358
1409
  startedAt: previous?.startedAt ?? Date.now(),
@@ -1368,7 +1419,60 @@ function apply(ctx, config) {
1368
1419
  const MAX_REQUEST_BYTES = 8 * 1024;
1369
1420
  const selfChecks = /* @__PURE__ */ new Map();
1370
1421
  const SELF_CHECK_COOLDOWN_MS = 3e3;
1422
+ const MAX_CHECK_ENTRIES_PER_PROVIDER = 8;
1423
+ const MAX_CHECK_PROVIDERS = 64;
1371
1424
  const lastChecks = /* @__PURE__ */ new Map();
1425
+ const checkKeyOf = (provider, key) => key.slice(provider.length);
1426
+ const pruneCheckCache = (now = Date.now()) => {
1427
+ for (const [provider, entries] of lastChecks) {
1428
+ for (const [key, entry] of entries) {
1429
+ if (now - entry.at >= SELF_CHECK_COOLDOWN_MS) entries.delete(key);
1430
+ }
1431
+ if (entries.size === 0) lastChecks.delete(provider);
1432
+ }
1433
+ while (lastChecks.size > MAX_CHECK_PROVIDERS) {
1434
+ let oldestProvider;
1435
+ let oldestAt = Infinity;
1436
+ for (const [provider, entries] of lastChecks) {
1437
+ let latest = -Infinity;
1438
+ for (const entry of entries.values()) latest = Math.max(latest, entry.at);
1439
+ if (latest < oldestAt) {
1440
+ oldestAt = latest;
1441
+ oldestProvider = provider;
1442
+ }
1443
+ }
1444
+ if (oldestProvider === void 0) break;
1445
+ lastChecks.delete(oldestProvider);
1446
+ }
1447
+ };
1448
+ const rememberCheck = (provider, key, result) => {
1449
+ pruneCheckCache();
1450
+ let entries = lastChecks.get(provider);
1451
+ if (entries === void 0) {
1452
+ entries = /* @__PURE__ */ new Map();
1453
+ lastChecks.set(provider, entries);
1454
+ }
1455
+ entries.set(checkKeyOf(provider, key), { at: Date.now(), result });
1456
+ while (entries.size > MAX_CHECK_ENTRIES_PER_PROVIDER) {
1457
+ let oldestKey;
1458
+ let oldestAt = Infinity;
1459
+ for (const [candidate, entry] of entries) {
1460
+ if (entry.at < oldestAt) {
1461
+ oldestAt = entry.at;
1462
+ oldestKey = candidate;
1463
+ }
1464
+ }
1465
+ if (oldestKey === void 0) break;
1466
+ entries.delete(oldestKey);
1467
+ }
1468
+ pruneCheckCache();
1469
+ };
1470
+ const forgetChecks = (provider) => {
1471
+ lastChecks.delete(provider);
1472
+ for (const key of [...selfChecks.keys()]) {
1473
+ if (key.startsWith(`${provider}\0`)) selfChecks.delete(key);
1474
+ }
1475
+ };
1372
1476
  const plainTestUrls = () => {
1373
1477
  const raw = config?.testUrl;
1374
1478
  const snapshot2 = raw?.get;
@@ -1426,12 +1530,15 @@ function apply(ctx, config) {
1426
1530
  }
1427
1531
  return out;
1428
1532
  };
1429
- const classifyTestError = (error, viaProxy) => {
1533
+ const classifyTestError = (error, viaProxy, rawTarget) => {
1430
1534
  const chain = causesOf(error);
1431
1535
  const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
1432
1536
  const codes = chain.map((node) => node.code).filter((code2) => typeof code2 === "string");
1433
1537
  const messages = chain.map((node) => node.message).filter((text) => typeof text === "string");
1434
- const message = error instanceof Error ? error.message : String(error);
1538
+ const message = redactTargetFromText(
1539
+ error instanceof Error ? error.message : String(error),
1540
+ rawTarget
1541
+ );
1435
1542
  if (names.includes("TimeoutError") || names.includes("AbortError")) {
1436
1543
  return { kind: "timeout", error: `\u8D85\u65F6\uFF08${TEST_TIMEOUT_MS / 1e3}s\uFF09\uFF1A\u4EE3\u7406\u6216\u76EE\u6807\u65E0\u54CD\u5E94` };
1437
1544
  }
@@ -1445,6 +1552,28 @@ function apply(ctx, config) {
1445
1552
  }
1446
1553
  return { kind: "target", error: `\u8BF7\u6C42\u5931\u8D25\uFF1A${message}` };
1447
1554
  };
1555
+ const sanitizeTargetForDisplay = (target) => {
1556
+ const raw = String(target ?? "");
1557
+ try {
1558
+ const url = new URL(raw);
1559
+ return `${url.protocol}//${url.host}${url.pathname}`;
1560
+ } catch {
1561
+ const withoutUserinfo = raw.replace(
1562
+ /^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/i,
1563
+ "$1"
1564
+ );
1565
+ const withoutQuery = withoutUserinfo.split(/[?#]/, 1)[0] ?? "";
1566
+ const bounded = withoutQuery.length > 160 ? `${withoutQuery.slice(0, 160)}\u2026` : withoutQuery;
1567
+ return bounded;
1568
+ }
1569
+ };
1570
+ const redactTargetFromText = (text, rawTarget) => {
1571
+ let safe = text;
1572
+ if (rawTarget.length > 0) safe = safe.split(rawTarget).join("[\u76EE\u6807\u5DF2\u8131\u654F]");
1573
+ safe = safe.replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
1574
+ safe = safe.replace(/([?&#])[^\s"'<>]*/g, "$1");
1575
+ return safe.length > 500 ? `${safe.slice(0, 500)}\u2026` : safe;
1576
+ };
1448
1577
  const failureCodeOf = (error) => {
1449
1578
  const chain = causesOf(error);
1450
1579
  const names = chain.map((node) => node.name).filter((name2) => typeof name2 === "string");
@@ -1457,24 +1586,45 @@ function apply(ctx, config) {
1457
1586
  return "ERROR";
1458
1587
  };
1459
1588
  const probeTarget = async (target, route) => {
1589
+ const displayTarget = sanitizeTargetForDisplay(target);
1460
1590
  let host;
1461
1591
  try {
1462
1592
  host = new URL(target).hostname;
1463
1593
  } catch {
1464
- return { ok: false, kind: "target", error: `\u81EA\u68C0\u76EE\u6807\u4E0D\u662F\u5408\u6CD5 URL\uFF1A${target}` };
1594
+ return {
1595
+ ok: false,
1596
+ kind: "target",
1597
+ target: displayTarget,
1598
+ error: `\u81EA\u68C0\u76EE\u6807\u4E0D\u662F\u5408\u6CD5 URL\uFF1A${displayTarget}`
1599
+ };
1465
1600
  }
1466
1601
  const viaProxy = route.decide(host) === "proxy";
1467
1602
  const started = performance.now();
1468
1603
  try {
1469
- const response = await undiciFetch2(target, {
1604
+ const requestTarget = (() => {
1605
+ const url = new URL(target);
1606
+ url.username = "";
1607
+ url.password = "";
1608
+ return url.toString();
1609
+ })();
1610
+ const response = await undiciFetch2(requestTarget, {
1470
1611
  // A direct probe must NOT carry the dispatcher, or undici would force it
1471
1612
  // through the proxy anyway.
1472
1613
  ...viaProxy ? { dispatcher: route.dispatcher } : {},
1473
1614
  signal: AbortSignal.timeout(TEST_TIMEOUT_MS)
1474
1615
  });
1475
1616
  const ms = Math.round(performance.now() - started);
1617
+ const targetField = { target: displayTarget };
1476
1618
  if (viaProxy && response.status === 407) {
1477
- return { ok: false, ms, status: 407, kind: "proxy-auth", viaProxy, error: "\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09" };
1619
+ return {
1620
+ ok: false,
1621
+ ms,
1622
+ status: 407,
1623
+ kind: "proxy-auth",
1624
+ viaProxy,
1625
+ ...targetField,
1626
+ error: `\u4EE3\u7406\u8981\u6C42\u8BA4\u8BC1\uFF08HTTP 407\uFF09\uFF1A${displayTarget}`
1627
+ };
1478
1628
  }
1479
1629
  let exitIp;
1480
1630
  try {
@@ -1489,6 +1639,7 @@ function apply(ctx, config) {
1489
1639
  ms,
1490
1640
  status: response.status,
1491
1641
  viaProxy,
1642
+ ...targetField,
1492
1643
  ...exitIp === void 0 ? {} : { exitIp },
1493
1644
  kind: "ok"
1494
1645
  };
@@ -1498,24 +1649,28 @@ function apply(ctx, config) {
1498
1649
  ms,
1499
1650
  status: response.status,
1500
1651
  viaProxy,
1652
+ ...targetField,
1501
1653
  kind: "target",
1502
- error: `\u76EE\u6807\u8FD4\u56DE HTTP ${response.status}\uFF085xx\uFF0C\u4E0A\u6E38\u6216\u94FE\u8DEF\u5F02\u5E38\uFF09`
1654
+ // The status travels in its own field; the reason text must not repeat
1655
+ // it, or the page shows the same code twice in different colours.
1656
+ error: `\u76EE\u6807\u8FD4\u56DE 5xx\uFF08\u4E0A\u6E38\u6216\u94FE\u8DEF\u5F02\u5E38\uFF09\uFF1A${displayTarget}`
1503
1657
  };
1504
1658
  } catch (error) {
1505
1659
  return {
1506
1660
  ok: false,
1507
1661
  ms: Math.round(performance.now() - started),
1508
1662
  viaProxy,
1509
- ...classifyTestError(error, viaProxy)
1663
+ ...classifyTestError(error, viaProxy, target),
1664
+ target: displayTarget
1510
1665
  };
1511
1666
  }
1512
1667
  };
1513
- const selfCheckTargets = (provider, testTarget) => {
1668
+ const selfCheckTargetsOf = (provider, testTarget) => {
1514
1669
  const own = (testTarget ?? "").trim();
1515
- if (own.length > 0) return [own];
1670
+ if (own.length > 0) return { targets: [own], source: "route" };
1516
1671
  const upstream = upstreamOf(provider);
1517
- if (upstream !== void 0) return [upstream];
1518
- return plainTestUrls();
1672
+ if (upstream !== void 0) return { targets: [upstream], source: "upstream" };
1673
+ return { targets: plainTestUrls(), source: "testUrl" };
1519
1674
  };
1520
1675
  const selfCheckKey = (provider) => {
1521
1676
  const raw = plainProviders(config)[provider] ?? {};
@@ -1531,7 +1686,7 @@ function apply(ctx, config) {
1531
1686
  raw.onlyHosts ?? "",
1532
1687
  raw.bypassHosts ?? "",
1533
1688
  plainLocalDirect(config),
1534
- selfCheckTargets(provider, raw.testTarget)
1689
+ selfCheckTargetsOf(provider, raw.testTarget).targets
1535
1690
  ]);
1536
1691
  };
1537
1692
  const runSelfCheck = async (provider) => {
@@ -1559,13 +1714,14 @@ function apply(ctx, config) {
1559
1714
  }
1560
1715
  try {
1561
1716
  let last;
1562
- for (const target of selfCheckTargets(provider, route.testTarget)) {
1717
+ const { targets, source } = selfCheckTargetsOf(provider, route.testTarget);
1718
+ for (const target of targets) {
1563
1719
  const outcome = await probeTarget(target, route);
1564
- if (outcome.ok === true) return outcome;
1720
+ if (outcome.ok === true) return { ...outcome, targetSource: source };
1565
1721
  last = outcome;
1566
1722
  if (outcome.kind === "proxy-auth") break;
1567
1723
  }
1568
- return last ?? { ok: false, kind: "target", error: "\u6CA1\u6709\u53EF\u7528\u7684\u81EA\u68C0\u76EE\u6807" };
1724
+ return last === void 0 ? { ok: false, kind: "target", target: "", targetSource: source, error: "\u6CA1\u6709\u53EF\u7528\u7684\u81EA\u68C0\u76EE\u6807" } : { ...last, targetSource: source };
1569
1725
  } finally {
1570
1726
  if (temporary) await route.dispatcher.close().catch(() => void 0);
1571
1727
  }
@@ -1574,17 +1730,56 @@ function apply(ctx, config) {
1574
1730
  ctx.logger.info("dsh-provider-proxy mounted (pid %d)", process.pid);
1575
1731
  } catch {
1576
1732
  }
1577
- const rebuild = async () => {
1578
- const next = createProxyTable(plainProviders(config), {
1579
- defaultProxy: plainDefaultProxy(config),
1580
- localDirect: plainLocalDirect(config)
1581
- });
1733
+ const rebuild = () => {
1734
+ const providers = plainProviders(config);
1735
+ const defaultProxy = plainDefaultProxy(config);
1736
+ const localDirect = plainLocalDirect(config);
1737
+ const next = createProxyTable(providers, { defaultProxy, localDirect });
1582
1738
  const previous = current;
1583
1739
  current = next.entries;
1584
1740
  problems = next.problems;
1741
+ const configured = new Set(Object.keys(providers));
1742
+ for (const provider of [...routeStates.keys()]) {
1743
+ if (configured.has(provider)) continue;
1744
+ routeStates.delete(provider);
1745
+ lastUse.delete(provider);
1746
+ forgetChecks(provider);
1747
+ }
1748
+ for (const [provider, raw] of Object.entries(providers)) {
1749
+ const effective = raw?.useDefault === true ? { ...raw, ...defaultProxy } : raw;
1750
+ const fingerprint = routeFingerprint({
1751
+ ...effective,
1752
+ localDirect
1753
+ });
1754
+ const decision = routeStateDecision(routeStates.get(provider), {
1755
+ fingerprint,
1756
+ enabled: raw?.enabled === true
1757
+ });
1758
+ const token = decision.token === "new" ? nextRouteToken++ : routeStates.get(provider).token;
1759
+ if (decision.clearLastUse) {
1760
+ lastUse.delete(provider);
1761
+ forgetChecks(provider);
1762
+ }
1763
+ routeStates.set(provider, { fingerprint, enabled: raw?.enabled === true, token });
1764
+ }
1765
+ pruneCheckCache();
1585
1766
  void closeTable(previous);
1767
+ return Promise.resolve();
1586
1768
  };
1587
- const ready = rebuild();
1769
+ let rebuildQueue = Promise.resolve();
1770
+ const enqueueRebuild = () => {
1771
+ if (closed) return Promise.resolve();
1772
+ try {
1773
+ const done = rebuild();
1774
+ rebuildQueue = rebuildQueue.then(() => done).catch(() => void 0);
1775
+ return done;
1776
+ } catch (error) {
1777
+ const failed = Promise.reject(error);
1778
+ rebuildQueue = failed.catch(() => void 0);
1779
+ return failed;
1780
+ }
1781
+ };
1782
+ enqueueRebuild();
1588
1783
  const catalog = () => {
1589
1784
  const live = /* @__PURE__ */ new Set();
1590
1785
  const out = [];
@@ -1700,16 +1895,20 @@ function apply(ctx, config) {
1700
1895
  send(400, { ok: false, kind: "body", error: "\u7F3A\u5C11 provider" });
1701
1896
  return;
1702
1897
  }
1703
- const key = selfCheckKey(provider);
1898
+ const key = `${provider}\0${selfCheckKey(provider)}`;
1704
1899
  let pending = selfChecks.get(key);
1705
1900
  if (pending === void 0) {
1706
- const last = lastChecks.get(key);
1901
+ pruneCheckCache();
1902
+ const last = lastChecks.get(provider)?.get(checkKeyOf(provider, key));
1707
1903
  if (last !== void 0 && Date.now() - last.at < SELF_CHECK_COOLDOWN_MS) {
1708
1904
  send(200, { ...last.result, cached: true });
1709
1905
  return;
1710
1906
  }
1907
+ const checkToken = routeStates.get(provider)?.token;
1711
1908
  pending = runSelfCheck(provider).then((result) => {
1712
- lastChecks.set(key, { at: Date.now(), result });
1909
+ if (checkToken !== void 0 && routeStates.get(provider)?.token === checkToken) {
1910
+ rememberCheck(provider, key, result);
1911
+ }
1713
1912
  return result;
1714
1913
  });
1715
1914
  selfChecks.set(key, pending);
@@ -1736,11 +1935,13 @@ function apply(ctx, config) {
1736
1935
  const patched = function(request, signal) {
1737
1936
  const route = current.get(WEB_SEARCH_KEY);
1738
1937
  if (route === void 0) return original.call(this, request, signal);
1938
+ const token = routeStates.get(WEB_SEARCH_KEY)?.token;
1939
+ if (token === void 0) return original.call(this, request, signal);
1739
1940
  return router.run(
1740
1941
  {
1741
1942
  dispatcher: route.dispatcher,
1742
- onHit: () => bump(WEB_SEARCH_KEY),
1743
- onSettle: settle(WEB_SEARCH_KEY),
1943
+ onHit: () => bump(WEB_SEARCH_KEY, token),
1944
+ onSettle: settle(WEB_SEARCH_KEY, token),
1744
1945
  decide: route.decide
1745
1946
  },
1746
1947
  () => original.call(this, request, signal)
@@ -1760,11 +1961,13 @@ function apply(ctx, config) {
1760
1961
  if (typeof provider !== "string") return next();
1761
1962
  const route = current.get(provider);
1762
1963
  if (route === void 0) return next();
1964
+ const token = routeStates.get(provider)?.token;
1965
+ if (token === void 0) return next();
1763
1966
  return router.wrap(
1764
1967
  {
1765
1968
  dispatcher: route.dispatcher,
1766
- onHit: () => bump(provider),
1767
- onSettle: settle(provider),
1969
+ onHit: () => bump(provider, token),
1970
+ onSettle: settle(provider, token),
1768
1971
  decide: route.decide
1769
1972
  },
1770
1973
  next
@@ -1773,14 +1976,14 @@ function apply(ctx, config) {
1773
1976
  { global: true }
1774
1977
  );
1775
1978
  const onConfig = () => {
1776
- void rebuild();
1979
+ void enqueueRebuild();
1777
1980
  };
1778
1981
  ctx.on("loader/volatile-update", onConfig, { global: true });
1779
1982
  return async () => {
1780
1983
  stop();
1781
1984
  if (closed) return;
1782
1985
  closed = true;
1783
- await ready.catch(() => void 0);
1986
+ await rebuildQueue.catch(() => void 0);
1784
1987
  await closeTable(current);
1785
1988
  current = /* @__PURE__ */ new Map();
1786
1989
  router.release();
@@ -1795,7 +1998,26 @@ function apply(ctx, config) {
1795
1998
  })),
1796
1999
  problems: Object.fromEntries(problems),
1797
2000
  hits: Object.fromEntries(hits),
1798
- lastUse: Object.fromEntries(lastUse)
2001
+ lastUse: Object.fromEntries(lastUse),
2002
+ selfCheckCache: (() => {
2003
+ let expiredEntries = 0;
2004
+ let maxPerProvider = 0;
2005
+ let totalEntries = 0;
2006
+ for (const entries of lastChecks.values()) {
2007
+ totalEntries += entries.size;
2008
+ maxPerProvider = Math.max(maxPerProvider, entries.size);
2009
+ for (const entry of entries.values()) {
2010
+ if (Date.now() - entry.at >= SELF_CHECK_COOLDOWN_MS) expiredEntries += 1;
2011
+ }
2012
+ }
2013
+ return {
2014
+ providers: lastChecks.size,
2015
+ pending: selfChecks.size,
2016
+ expiredEntries,
2017
+ totalEntries,
2018
+ maxPerProvider
2019
+ };
2020
+ })()
1799
2021
  })
1800
2022
  });
1801
2023
  }
@@ -1809,12 +2031,19 @@ export {
1809
2031
  buildRouteEntry,
1810
2032
  closeTable,
1811
2033
  createProxyTable,
2034
+ deadRuleEntries,
1812
2035
  defaultProxyProblem,
2036
+ hostMatches,
2037
+ hostRuleProblem,
1813
2038
  inject,
1814
2039
  name,
1815
2040
  normalizeDefaultProxy,
1816
2041
  normalizeRoute,
2042
+ parseHostList,
1817
2043
  proxyHostProblem,
1818
- proxyUrl
2044
+ proxyUrl,
2045
+ routeFingerprint,
2046
+ routeStateDecision,
2047
+ splitHostRules
1819
2048
  };
1820
2049
  //# sourceMappingURL=index.js.map