Passed
Push — main ( 085602...a6fcf3 )
by Yuri
01:24 queued 14s
created

src/index.ts   A

Complexity

Total Complexity 19
Complexity/F 2.71

Size

Lines of Code 109
Function Count 7

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 19
eloc 82
mnd 12
bc 12
fnc 7
dl 0
loc 109
bpm 1.7142
cpm 2.7142
noi 0
c 0
b 0
f 0
ccs 37
cts 37
cp 1
rs 10

7 Functions

Rating   Name   Duplication   Size   Complexity  
A index.ts ➔ getExchangeRate 0 21 3
A index.ts ➔ fetchExchangeRate 0 12 4
A index.ts ➔ buildRateUrl 0 3 1
A index.ts ➔ fetchExchangeRateResponse 0 7 2
A index.ts ➔ isCacheValid 0 6 1
A index.ts ➔ getCachedRate 0 12 3
A index.ts ➔ extractRateFromResponse 0 7 5
1 2
import { DataError, NetworkError, ServerError } from "./errors";
2
3
interface ExchangeRateResponse {
4
  chart: {
5
    result: [
6
      {
7
        meta: {
8
          regularMarketPrice: number;
9
        };
10
      },
11
    ];
12
  };
13
}
14
15
interface CachedRate {
16
  value: number;
17
  timestamp: number;
18
}
19
20
interface ExchangeRateOptions {
21
  cacheDurationMs?: number;
22
}
23
24
const YAHOO_FINANCE_BASE_URL =
25 2
  "https://query1.finance.yahoo.com/v8/finance/chart/";
26
const YAHOO_FINANCE_QUERY_PARAMS =
27 2
  "=X?region=US&lang=en-US&includePrePost=false&interval=2m&useYfid=true&range=1d&corsDomain=finance.yahoo.com&.tsrc=finance";
28
29 2
const rateCache = new Map<string, CachedRate>();
30
31
function buildRateUrl(fromCurrency: string, toCurrency: string): string {
32 19
  return `${YAHOO_FINANCE_BASE_URL}${fromCurrency.toUpperCase()}${toCurrency.toUpperCase()}${YAHOO_FINANCE_QUERY_PARAMS}`;
33
}
34
35
function isCacheValid(
36
  cachedRate: CachedRate,
37
  cacheDurationMs: number,
38
): boolean {
39 3
  return Date.now() - cachedRate.timestamp < cacheDurationMs;
40
}
41
42
function getCachedRate(
43
  fromCurrency: string,
44
  toCurrency: string,
45
  cacheDurationMs?: number,
46
): number | null {
47 21
  if (!cacheDurationMs) return null;
48 6
  const cacheKey = `${fromCurrency}-${toCurrency}`;
49 6
  const cachedRate = rateCache.get(cacheKey);
50 6
  return cachedRate && isCacheValid(cachedRate, cacheDurationMs)
51
    ? cachedRate.value
52
    : null;
53
}
54
55
async function fetchExchangeRateResponse(rateUrl: string): Promise<Response> {
56 19
  const response = await fetch(rateUrl);
57 15
  if (!response.ok) {
58 2
    throw new ServerError(`HTTP ${response.status}: ${response.statusText}`);
59
  }
60 13
  return response;
61
}
62
63
function extractRateFromResponse(response: ExchangeRateResponse): number {
64 13
  const rate = response.chart?.result[0]?.meta?.regularMarketPrice;
65 13
  if (typeof rate !== "number" || isNaN(rate)) {
66 1
    throw new DataError('Invalid or missing "regularMarketPrice" in response.');
67
  }
68 12
  return rate;
69
}
70
71
async function fetchExchangeRate(rateUrl: string): Promise<number> {
72 19
  try {
73 19
    const response = await fetchExchangeRateResponse(rateUrl);
74 13
    const responseData: ExchangeRateResponse = await response.json();
75 13
    return extractRateFromResponse(responseData);
76
  } catch (error) {
77 7
    if (error instanceof ServerError || error instanceof DataError) {
78 3
      throw error;
79
    }
80 4
    throw new NetworkError(
81
      `Failed to fetch exchange rate: ${error instanceof Error ? error.message : String(error)}`,
82
    );
83
  }
84
}
85
86 2
export async function getExchangeRate(
87
  fromCurrency: string,
88
  toCurrency: string,
89
  options: ExchangeRateOptions = {},
90
): Promise<number> {
91 21
  const { cacheDurationMs } = options;
92 21
  const cachedRate = getCachedRate(fromCurrency, toCurrency, cacheDurationMs);
93 21
  if (cachedRate !== null) {
94 2
    return cachedRate;
95
  }
96
97 19
  const rateUrl = buildRateUrl(fromCurrency, toCurrency);
98 19
  const rate = await fetchExchangeRate(rateUrl);
99
100 12
  if (cacheDurationMs) {
101 4
    const cacheKey = `${fromCurrency}-${toCurrency}`;
102 4
    rateCache.set(cacheKey, { value: rate, timestamp: Date.now() });
103
  }
104
105 12
  return rate;
106
}
107
108
export * from "./errors";
109