tokmon 0.20.2 → 0.20.4

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.
@@ -18,7 +18,7 @@ import {
18
18
  systemTimezone,
19
19
  time,
20
20
  tokens
21
- } from "./chunk-7HJIP4U6.js";
21
+ } from "./chunk-HSUYWU4V.js";
22
22
  import {
23
23
  COLOR_PALETTE,
24
24
  DEFAULTS,
@@ -1670,12 +1670,11 @@ async function socketLayerFor(url, transport) {
1670
1670
  );
1671
1671
  }
1672
1672
  function retryPolicy(options) {
1673
- const attempts = options.reconnectAttempts ?? 5;
1674
1673
  const baseDelay = options.reconnectBaseDelayMs ?? 250;
1675
- return Schedule.addDelay(
1676
- Schedule.recurs(attempts),
1677
- (retryCount) => Effect.succeed(Duration.millis(Math.min(baseDelay * (retryCount + 1), 2500)))
1674
+ const policy = Schedule.exponential(Duration.millis(baseDelay), 1.5).pipe(
1675
+ Schedule.either(Schedule.spaced(Duration.millis(2500)))
1678
1676
  );
1677
+ return typeof options.reconnectAttempts === "number" ? policy.pipe(Schedule.both(Schedule.recurs(options.reconnectAttempts))) : policy;
1679
1678
  }
1680
1679
  function createDaemonRpcClient(baseUrl, options = {}) {
1681
1680
  const url = toWsUrl(baseUrl, options.wsToken);
@@ -1686,6 +1685,13 @@ function createDaemonRpcClient(baseUrl, options = {}) {
1686
1685
  const setConn = (state, error) => {
1687
1686
  options.onConn?.(state, error);
1688
1687
  };
1688
+ const resetSession = (active) => {
1689
+ const dead = active ?? session;
1690
+ if (active === void 0 || active === session) session = null;
1691
+ sessionPromise = null;
1692
+ if (dead) void dead.runtime.dispose().catch(() => {
1693
+ });
1694
+ };
1689
1695
  const makeProtocolLayer = async () => {
1690
1696
  const socketLayer = await socketLayerFor(url, options.transport);
1691
1697
  const connectionHooksLayer = Layer.succeed(
@@ -1695,7 +1701,10 @@ function createDaemonRpcClient(baseUrl, options = {}) {
1695
1701
  setConn("live");
1696
1702
  }),
1697
1703
  onDisconnect: Effect.sync(() => {
1698
- if (!closed) setConn("reconnecting");
1704
+ if (!closed) {
1705
+ setConn("reconnecting");
1706
+ resetSession();
1707
+ }
1699
1708
  })
1700
1709
  })
1701
1710
  );
@@ -1749,7 +1758,10 @@ function createDaemonRpcClient(baseUrl, options = {}) {
1749
1758
  TokmonRpcClient.use((client) => effectFor(client))
1750
1759
  );
1751
1760
  } catch (error) {
1752
- if (!closed) setConn("error", error);
1761
+ if (!closed) {
1762
+ resetSession(active);
1763
+ setConn("error", error);
1764
+ }
1753
1765
  throw error;
1754
1766
  }
1755
1767
  };
@@ -1758,42 +1770,71 @@ function createDaemonRpcClient(baseUrl, options = {}) {
1758
1770
  };
1759
1771
  let fiber = null;
1760
1772
  let unsubscribed = false;
1761
- void (async () => {
1762
- try {
1763
- const active = await ensureSession();
1764
- if (closed || unsubscribed) return;
1765
- fiber = active.runtime.runFork(
1766
- TokmonRpcClient.use(
1767
- (client) => streamFor(client).pipe(
1768
- Stream.runForEach(
1769
- (value) => Effect.sync(() => {
1770
- try {
1771
- onValue(value);
1772
- } catch {
1773
- }
1774
- })
1773
+ let retryTimer = null;
1774
+ const stopFiber = () => {
1775
+ if (!fiber) return;
1776
+ const current = fiber;
1777
+ fiber = null;
1778
+ fibers.delete(current);
1779
+ void (session?.runtime.runPromise(Fiber.interrupt(current)) ?? Effect.runPromise(Fiber.interrupt(current))).catch(() => {
1780
+ });
1781
+ };
1782
+ const scheduleRetry = () => {
1783
+ if (closed || unsubscribed || retryTimer) return;
1784
+ retryTimer = setTimeout(() => {
1785
+ retryTimer = null;
1786
+ start();
1787
+ }, options.reconnectBaseDelayMs ?? 250);
1788
+ retryTimer.unref?.();
1789
+ };
1790
+ const start = () => {
1791
+ void (async () => {
1792
+ try {
1793
+ const active = await ensureSession();
1794
+ if (closed || unsubscribed) return;
1795
+ fiber = active.runtime.runFork(
1796
+ TokmonRpcClient.use(
1797
+ (client) => streamFor(client).pipe(
1798
+ Stream.runForEach(
1799
+ (value) => Effect.sync(() => {
1800
+ try {
1801
+ onValue(value);
1802
+ } catch {
1803
+ }
1804
+ })
1805
+ )
1775
1806
  )
1776
- )
1777
- ).pipe(Effect.catchCause(
1778
- (cause) => Effect.sync(() => {
1779
- if (!closed && !unsubscribed) setConn("error", Cause.squash(cause));
1780
- })
1781
- ))
1782
- );
1783
- fibers.add(fiber);
1784
- fiber.addObserver(() => {
1785
- if (fiber) fibers.delete(fiber);
1786
- });
1787
- } catch (error) {
1788
- if (!closed && !unsubscribed) setConn("error", error);
1789
- }
1790
- })();
1807
+ ).pipe(Effect.catchCause(
1808
+ (cause) => Effect.sync(() => {
1809
+ if (!closed && !unsubscribed) {
1810
+ resetSession(active);
1811
+ setConn("error", Cause.squash(cause));
1812
+ scheduleRetry();
1813
+ }
1814
+ })
1815
+ ))
1816
+ );
1817
+ fibers.add(fiber);
1818
+ fiber.addObserver(() => {
1819
+ if (fiber) fibers.delete(fiber);
1820
+ });
1821
+ } catch (error) {
1822
+ if (!closed && !unsubscribed) {
1823
+ resetSession();
1824
+ setConn("error", error);
1825
+ scheduleRetry();
1826
+ }
1827
+ }
1828
+ })();
1829
+ };
1830
+ start();
1791
1831
  return () => {
1792
1832
  unsubscribed = true;
1793
- if (!fiber) return;
1794
- fibers.delete(fiber);
1795
- void (session?.runtime.runPromise(Fiber.interrupt(fiber)) ?? Effect.runPromise(Fiber.interrupt(fiber))).catch(() => {
1796
- });
1833
+ if (retryTimer) {
1834
+ clearTimeout(retryTimer);
1835
+ retryTimer = null;
1836
+ }
1837
+ stopFiber();
1797
1838
  };
1798
1839
  };
1799
1840
  return {
@@ -3194,7 +3235,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3194
3235
  if (webStartingRef.current) return;
3195
3236
  webStartingRef.current = true;
3196
3237
  try {
3197
- const { startWebServer } = await import("./server-BXMRN774.js");
3238
+ const { startWebServer } = await import("./server-6DGQI25X.js");
3198
3239
  const ctrl = await startWebServer({ config: cfg, log: false });
3199
3240
  webRef.current = ctrl;
3200
3241
  openUrl(ctrl.url);
@@ -8,7 +8,7 @@ import {
8
8
  detectProviders,
9
9
  fetchPeak,
10
10
  resolveTimezone
11
- } from "./chunk-7HJIP4U6.js";
11
+ } from "./chunk-HSUYWU4V.js";
12
12
  import {
13
13
  cacheDir,
14
14
  expandHome,
@@ -319,6 +319,14 @@ var PEAK_INTERVAL_MS = 3e5;
319
319
  var IDLE_PAUSE_MS = 6e4;
320
320
  var SNAPSHOT_CACHE_THROTTLE_MS = 2e4;
321
321
  var REVEAL_THROTTLE_MS = 500;
322
+ var FETCH_TIMEOUT_MS = 3e4;
323
+ var withTimeout = (p, ms) => Promise.race([
324
+ p,
325
+ new Promise((_, reject) => {
326
+ const t = setTimeout(() => reject(new Error("fetch timeout")), ms);
327
+ t.unref?.();
328
+ })
329
+ ]);
322
330
  function createDataEngine(opts) {
323
331
  const { version } = opts;
324
332
  let tz = opts.tz;
@@ -438,7 +446,7 @@ function createDataEngine(opts) {
438
446
  let dashboard = null;
439
447
  let ok = true;
440
448
  try {
441
- dashboard = await fetchAccountSummary(r.account, tz);
449
+ dashboard = await withTimeout(fetchAccountSummary(r.account, tz), FETCH_TIMEOUT_MS);
442
450
  } catch {
443
451
  ok = false;
444
452
  }
@@ -475,7 +483,7 @@ function createDataEngine(opts) {
475
483
  let table = null;
476
484
  let ok = true;
477
485
  try {
478
- table = await fetchAccountTable(r.account, tz);
486
+ table = await withTimeout(fetchAccountTable(r.account, tz), FETCH_TIMEOUT_MS);
479
487
  } catch {
480
488
  ok = false;
481
489
  }
@@ -512,7 +520,7 @@ function createDataEngine(opts) {
512
520
  let result = null;
513
521
  let ok = true;
514
522
  try {
515
- result = await fetchAccountBilling(r.account);
523
+ result = await withTimeout(fetchAccountBilling(r.account), FETCH_TIMEOUT_MS);
516
524
  } catch {
517
525
  ok = false;
518
526
  }
@@ -702,6 +710,10 @@ import { join as join3, resolve as resolvePath, isAbsolute, sep as sep2 } from "
702
710
  function isContained(root, target) {
703
711
  return target === root || target.startsWith(root + sep2);
704
712
  }
713
+ function parentFor(root, abs) {
714
+ const parentResolved = resolvePath(abs, "..");
715
+ return abs === root || !isContained(root, parentResolved) ? null : parentResolved;
716
+ }
705
717
  async function listHomeDirectory(rawPath) {
706
718
  const root = resolvePath(homedir());
707
719
  const expanded = expandHome(rawPath || "~");
@@ -714,9 +726,19 @@ async function listHomeDirectory(rawPath) {
714
726
  real = lexical;
715
727
  }
716
728
  const abs = isContained(root, real) ? real : root;
717
- const st = await stat2(abs);
718
- if (!st.isDirectory()) throw new Error("not a directory");
719
- const dirents = await readdir(abs, { withFileTypes: true });
729
+ let st;
730
+ try {
731
+ st = await stat2(abs);
732
+ } catch {
733
+ return { path: abs, parent: parentFor(root, abs), entries: [] };
734
+ }
735
+ if (!st.isDirectory()) return { path: abs, parent: parentFor(root, abs), entries: [] };
736
+ let dirents;
737
+ try {
738
+ dirents = await readdir(abs, { withFileTypes: true });
739
+ } catch {
740
+ return { path: abs, parent: parentFor(root, abs), entries: [] };
741
+ }
720
742
  const entries = [];
721
743
  for (const d of dirents) {
722
744
  if (d.name.startsWith(".")) continue;
@@ -739,9 +761,7 @@ async function listHomeDirectory(rawPath) {
739
761
  entries.push({ name: d.name, path: full, dir });
740
762
  }
741
763
  entries.sort((a, b) => a.dir === b.dir ? a.name.localeCompare(b.name) : a.dir ? -1 : 1);
742
- const parentResolved = resolvePath(abs, "..");
743
- const parent = abs === root || !isContained(root, parentResolved) ? null : parentResolved;
744
- return { path: abs, parent, entries };
764
+ return { path: abs, parent: parentFor(root, abs), entries };
745
765
  }
746
766
 
747
767
  // src/web/ws.ts
@@ -841,12 +861,12 @@ async function mountWsRpc(server, deps) {
841
861
  const wss = new NodeWS.WebSocketServer({ noServer: true });
842
862
  const handlersLayer = TokmonRpcGroup.toLayer(
843
863
  TokmonRpcGroup.of({
844
- [TOKMON_WS_METHODS.getConfig]: () => Effect.promise(async () => deps.state.config ?? await loadConfig()),
845
- [TOKMON_WS_METHODS.setConfig]: (config) => Effect.promise(() => applyConfigUpdate(deps.engine, deps.state, config)),
864
+ [TOKMON_WS_METHODS.getConfig]: () => Effect.tryPromise(() => Promise.resolve(deps.state.config ?? loadConfig())),
865
+ [TOKMON_WS_METHODS.setConfig]: (config) => Effect.tryPromise(() => applyConfigUpdate(deps.engine, deps.state, config)),
846
866
  [TOKMON_WS_METHODS.refresh]: ({ scope: scope2 }) => Effect.sync(() => {
847
867
  deps.engine.refresh(scope2);
848
868
  }),
849
- [TOKMON_WS_METHODS.browseFs]: ({ path }) => Effect.promise(() => listHomeDirectory(path)),
869
+ [TOKMON_WS_METHODS.browseFs]: ({ path }) => Effect.tryPromise(() => listHomeDirectory(path)),
850
870
  [TOKMON_WS_METHODS.snapshot]: () => snapshotStream(deps.engine),
851
871
  [TOKMON_WS_METHODS.config]: () => configStream(deps.engine)
852
872
  })