nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions and classes for everyday development needs.
147 lines (146 loc) • 6.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Currency = void 0;
const utilities_1 = require("./utilities");
/**
* * A utility class for handling currency operations like formatting and conversion.
*
* - Supports formatting based on locale and currency code.
* - Converts between **fiat currencies supported by `api.frankfurter.app`**.
* - Automatically caches conversion rates to reduce redundant API calls.
* - Intended for use with numeric inputs (number or numeric string).
*/
class Currency {
#amount;
#code;
/**
* * The formatted currency string (e.g., `$1,000.00`).
*
* - Generated using the `en-US` locale during construction.
* - This is a display-friendly version of the currency value.
* - For formatting with other locales, use the `format()` method.
*/
currency;
/**
* Creates an instance of the Currency class.
*
* @param amount - The numeric amount of currency (e.g., `100`, `'99.99'`).
* @param code - The ISO 4217 currency code representing the currency (e.g., `'USD'`, `'EUR'`).
*/
constructor(amount, code) {
this.#amount = Number(amount);
this.#code = code;
this.currency = this.format('en-US');
}
static #rateCache = new Map();
/** * Clears cached rates that were fetched previously. */
static clearRateCache() {
Currency.#rateCache.clear();
}
/**
* @instance Formats the stored amount as a localized currency string.
*
* @param locale - Optional. A BCP 47 locale string (e.g., `'de-DE'`, `'en-US'`). Defaults to `'en-US'` if not provided.
* @param code - Optional. An ISO 4217 currency code (e.g., `'USD'`, `'EUR'`) used solely for formatting purposes.
* _This does not alter the internal currency code set during instantiation._
* @returns A string representing the formatted currency value according to the specified locale and currency code.
*/
format(locale, code) {
return (0, utilities_1.formatCurrency)(this.#amount, code ?? this.#code, locale);
}
/**
* @instance Converts the current currency amount to a target currency using real-time exchange rates.
*
* - Uses `api.frankfurter.app` to fetch live exchange rates.
* - Supports **only the following fiat currencies**:
* `AUD`, `BGN`, `BRL`, `CAD`, `CHF`, `CNY`, `CZK`, `DKK`, `EUR`, `GBP`, `HKD`, `HUF`, `IDR`, `ILS`, `INR`, `ISK`, `JPY`,
* `KRW`, `MXN`, `MYR`, `NOK`, `NZD`, `PHP`, `PLN`, `RON`, `SEK`, `SGD`, `THB`, `TRY`, `USD`, `ZAR`.
* - Uses cached rates unless `forceRefresh` is set to `true`.
* - If API fails or currency not supported, falls back to `fallbackRate` if provided.
*
* @param to - The target currency code (must be one of the supported ones, e.g., `'EUR'`, `'USD'`).
* @param options - Optional settings:
* - `fallbackRate`: A manual exchange rate to use if the API call fails or currency is not supported.
* - `forceRefresh`: If true, ignores cached rates and fetches fresh data.
* @returns A new `Currency` instance with the converted amount in the target currency.
* @throws Will throw error if the API call fails and no `fallbackRate` is provided.
*
* @example
* await new Currency(100, 'USD').convert('EUR');
*/
async convert(to, options) {
const key = `${this.#code}->${to}`;
if (!options?.forceRefresh && Currency.#rateCache.has(key)) {
const cachedRate = Currency.#rateCache.get(key);
return new Currency(this.#amount * cachedRate, to);
}
try {
const rate = await this.#fetchFromFrankfurter(to);
Currency.#rateCache.set(key, rate);
return new Currency(this.#amount * rate, to);
}
catch (error) {
if (options?.fallbackRate != null) {
console.warn(`Currency conversion failed (${this.#code} → ${to}): ${error.message}. Using fallback rate...`);
return new Currency(this.#amount * options.fallbackRate, to);
}
else {
throw new Error(`Currency conversion failed (${this.#code} → ${to}): ${error.message}`);
}
}
}
/**
* @instance Converts the current currency amount to a target currency using either a cached rate or a manual exchange rate.
*
* - This method is **synchronous** and does **not perform any network requests**.
* - If a cached rate exists for the currency pair, it is used.
* - If no cached rate is found, `rate` is used as a manual exchange rate.
* - If neither are available, the original instance is returned unchanged.
*
* @param to - The target currency code to convert to.
* @param rate - A manual exchange rate to use if no cached rate is available.
* @returns A new `Currency` instance with the converted amount, or the original instance if no rate is available.
*
* @example
* const usd = new Currency(100, 'USD');
* const eur = usd.convertSync('EUR', 0.92);
*
* console.log(eur.currency); // €92.00
*/
convertSync(to, rate) {
const key = `${this.#code}->${to}`;
const cachedRate = Currency.#rateCache.get(key);
if (cachedRate) {
return new Currency(this.#amount * cachedRate, to);
}
else if (rate) {
return new Currency(this.#amount * rate, to);
}
else {
return this;
}
}
/**
* @private Attempts to fetch rate from frankfurter.app
* @param to - Target currency code
* @returns Exchange rate (multiplier)
*/
async #fetchFromFrankfurter(to) {
const url = `https://api.frankfurter.app/latest?amount=${this.#amount}&from=${this.#code}`;
try {
const res = await fetch(url, { redirect: 'error' });
if (!res.ok)
throw new Error(`FrankFurter Error: ${res.status}. "${res.statusText}"`);
const data = await res.json();
if (!data.rates?.[to]) {
throw new Error(`Currency "${to}" not found in FrankFurter Database!`);
}
return data.rates[to] / this.#amount;
}
catch (error) {
throw new Error(error.message ||
`Failed to fetch data from FrankFurter API`);
}
}
}
exports.Currency = Currency;