UNPKG

currzy

Version:

Free, open-source library for fetching, managing, and converting up-to-date currency rates from multiple reliable sources with the ability to easily choose the source.

1 lines 12.8 kB
{"version":3,"sources":["../src/index.ts","../src/providers/cbrf/index.ts","../src/cache/drivers.ts","../src/cache/index.ts","../src/providers/cbrf/types/currency.ts"],"sourcesContent":["import { CbrfProvider } from \"@src/providers/cbrf\";\n\nexport { CbrfProvider };\n\nexport class Currzy {\n provider: CbrfProvider;\n\n constructor(providerName: string) {\n if (providerName === \"cbrf\") this.provider = new CbrfProvider();\n else throw new Error(\"Unknown provider\");\n }\n\n async getRate(code: string) {\n return await this.provider.getRate(code);\n }\n\n async convert(amount: number, from: string, to: string) {\n return await this.provider.convert(amount, from, to);\n }\n\n async getAllRatesTo(code?: string): Promise<Record<string, number | null>> {\n return await this.provider.getAllRates(code);\n }\n\n async clearCache(): Promise<void> {\n return this.provider.clearCache();\n }\n\n getLastUpdate(): Date | null {\n return this.provider.getLastUpdate();\n }\n}\n\n","import { ofetch } from \"ofetch\";\nimport { XMLParser } from \"fast-xml-parser\";\nimport { createCacheDriver } from \"@src/cache\";\nimport {\n Rate,\n CurrencyCode,\n AVAILABLE_CURRENCIES,\n assertCurrency,\n CbrfValute,\n CbrfData,\n CbrfCache,\n} from \"@src/providers/cbrf/types\";\n\nexport class CbrfProvider {\n private url =\n \"https://api.allorigins.win/raw?url=https://www.cbr.ru/scripts/XML_daily.asp\";\n\n private rates: Record<CurrencyCode, Rate> = {} as Record<CurrencyCode, Rate>;\n private lastUpdate: Date | null = null;\n private initialized = false;\n private cacheTTL = 1000 * 60 * 60;\n private cache = createCacheDriver<CbrfCache>(\"cbrf\");\n\n public availableCurrencies: readonly CurrencyCode[] = AVAILABLE_CURRENCIES;\n\n private async loadCache() {\n const data = await this.cache.load();\n if (data) {\n this.rates = data.payload.rates;\n this.lastUpdate = data.lastUpdate ? new Date(data.lastUpdate) : null;\n this.initialized = true;\n }\n }\n\n private async saveCache() {\n await this.cache.save({\n payload: { rates: this.rates },\n lastUpdate: this.lastUpdate ? this.lastUpdate.toISOString() : null,\n });\n }\n\n public async clearCache() {\n await this.cache.clear();\n this.rates = {} as Record<CurrencyCode, Rate>;\n this.lastUpdate = null;\n this.initialized = false;\n }\n\n private async fetchRates(): Promise<void> {\n try {\n const xml = await ofetch(this.url, { responseType: \"text\" });\n const parser = new XMLParser();\n const data: CbrfData = parser.parse(xml) as CbrfData;\n\n const items: CbrfValute[] = Array.isArray(data.ValCurs.Valute)\n ? data.ValCurs.Valute\n : [data.ValCurs.Valute];\n\n this.rates = {} as Record<CurrencyCode, Rate>;\n\n for (const item of items) {\n const nominal = Number(item.Nominal);\n const value = parseFloat(item.Value.replace(\",\", \".\"));\n const vunitRate = value / nominal;\n\n const rate: Rate = { code: item.CharCode, nominal, value, vunitRate };\n\n if (this.availableCurrencies.includes(rate.code as CurrencyCode)) {\n this.rates[rate.code as CurrencyCode] = rate;\n }\n }\n\n this.rates[\"RUB\"] = { code: \"RUB\", nominal: 1, value: 1, vunitRate: 1 };\n this.lastUpdate = new Date();\n this.initialized = true;\n await this.saveCache();\n } catch (e) {\n console.error(\"Error fetching CBRF rates:\", e);\n if (!this.initialized) {\n await this.loadCache();\n if (!this.initialized) throw new Error(\"No CBRF data and no cache\");\n }\n }\n }\n\n private async ensureInitialized(): Promise<void> {\n if (!this.initialized) await this.loadCache();\n\n const expired =\n this.lastUpdate === null ||\n new Date().getTime() - this.lastUpdate.getTime() > this.cacheTTL;\n\n if (!this.initialized || expired) {\n await this.fetchRates();\n }\n }\n\n async getRate(code: string, base: string = \"USD\"): Promise<number> {\n await this.ensureInitialized();\n assertCurrency(code);\n assertCurrency(base);\n\n const rateInRub = this.rates[code].vunitRate;\n const baseRateInRub = this.rates[base].vunitRate;\n\n return baseRateInRub / rateInRub;\n }\n\n async convert(amount: number, from: string, to: string): Promise<number> {\n await this.ensureInitialized();\n assertCurrency(from);\n assertCurrency(to);\n\n const fromRateInRub = this.rates[from].vunitRate;\n const toRateInRub = this.rates[to].vunitRate;\n\n return (amount * fromRateInRub) / toRateInRub;\n }\n\n async getAllRates(base: string = \"USD\"): Promise<Record<CurrencyCode, number>> {\n await this.ensureInitialized();\n assertCurrency(base);\n\n const result: Partial<Record<CurrencyCode, number>> = {};\n\n for (const code of this.availableCurrencies) {\n if (code === base) continue;\n result[code] = await this.getRate(code, base);\n }\n\n return result as Record<CurrencyCode, number>;\n }\n\n getLastUpdate(): Date | null {\n return this.lastUpdate;\n }\n}\n\n","import fs from \"fs/promises\";\nimport path from \"path\";\nimport type { CacheData, CacheDriver } from \"@src/cache/types\";\n\nexport class FileCacheDriver<T = unknown> implements CacheDriver<T> {\n private readonly cacheFile: string;\n\n constructor(providerName: string) {\n this.cacheFile = path.resolve(\"cache\", `${providerName}-cache.json`);\n }\n\n async load(): Promise<CacheData<T> | null> {\n try {\n const raw = await fs.readFile(this.cacheFile, \"utf-8\");\n return JSON.parse(raw) as CacheData<T>;\n } catch {\n return null;\n }\n }\n\n async save(data: CacheData<T>): Promise<void> {\n await fs.mkdir(path.dirname(this.cacheFile), { recursive: true });\n await fs.writeFile(this.cacheFile, JSON.stringify(data, null, 2), \"utf-8\");\n }\n\n async clear(): Promise<void> {\n try {\n await fs.unlink(this.cacheFile);\n } catch { /* empty */ }\n }\n}\n\nexport class LocalStorageCacheDriver<T = unknown> implements CacheDriver<T> {\n private readonly key: string;\n\n constructor(providerName: string) {\n this.key = `currenzy-${providerName}-cache`;\n }\n\n async load(): Promise<CacheData<T> | null> {\n const raw = localStorage.getItem(this.key);\n return raw ? (JSON.parse(raw) as CacheData<T>) : null;\n }\n\n async save(data: CacheData<T>): Promise<void> {\n localStorage.setItem(this.key, JSON.stringify(data));\n }\n\n async clear(): Promise<void> {\n localStorage.removeItem(this.key);\n }\n}\n","import { FileCacheDriver, LocalStorageCacheDriver } from \"./drivers\";\nimport type { CacheDriver } from \"@src/cache/types\";\n\nexport function createCacheDriver<T = unknown>(providerName: string): CacheDriver<T> {\n if (typeof window !== \"undefined\" && \"localStorage\" in window) {\n return new LocalStorageCacheDriver<T>(providerName);\n } else {\n return new FileCacheDriver<T>(providerName);\n }\n}\n","export const AVAILABLE_CURRENCIES = [\n \"AUD\", \"AZN\", \"DZD\", \"GBP\", \"AMD\", \"BHD\", \"BYN\", \"BGN\", \"BOB\", \"BRL\",\n \"HUF\", \"VND\", \"HKD\", \"GEL\", \"DKK\", \"AED\", \"USD\", \"EUR\", \"EGP\", \"INR\",\n \"IDR\", \"IRR\", \"KZT\", \"CAD\", \"QAR\", \"KGS\", \"CNY\", \"CUP\", \"MDL\", \"MNT\",\n \"NGN\", \"NZD\", \"NOK\", \"OMR\", \"PLN\", \"SAR\", \"RON\", \"XDR\", \"SGD\", \"TJS\",\n \"THB\", \"BDT\", \"TRY\", \"TMT\", \"UZS\", \"UAH\", \"CZK\", \"SEK\", \"CHF\", \"ETB\",\n \"RSD\", \"ZAR\", \"KRW\", \"JPY\", \"MMK\", \"RUB\"\n] as const;\n\nexport type CurrencyCode = (typeof AVAILABLE_CURRENCIES)[number];\n\nexport function assertCurrency(code: string): asserts code is CurrencyCode {\n if (!AVAILABLE_CURRENCIES.includes(code as CurrencyCode)) {\n throw new Error(`Unknown currency: ${code}`);\n }\n}\n"],"mappings":"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","names":["path","fs"]}