solve-engine 1.0.0-beta.7 → 1.0.0
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/dist/{BytecodeBuilder-3egMyuDi.d.cts → BytecodeBuilder-B0xskcv5.d.cts} +2 -1
- package/dist/{BytecodeBuilder-3egMyuDi.d.ts → BytecodeBuilder-B0xskcv5.d.ts} +2 -1
- package/dist/{Configuration-fVpd-HC-.d.cts → Configuration-B-G5gTRn.d.cts} +107 -29
- package/dist/{Configuration-fVpd-HC-.d.ts → Configuration-B-G5gTRn.d.ts} +107 -29
- package/dist/{EngineError-DvPSnhZi.d.cts → EngineError-LU7W7AgI.d.cts} +96 -16
- package/dist/{EngineError-DvPSnhZi.d.ts → EngineError-LU7W7AgI.d.ts} +96 -16
- package/dist/{Lexer-W9MBOP0V.d.ts → Lexer-BN5mt30n.d.ts} +16 -0
- package/dist/{Lexer-Cfm79Dak.d.cts → Lexer-D78E2yDW.d.cts} +16 -0
- package/dist/{PackageRegistry-CjDt-Jy_.d.ts → PackageRegistry-DFeINv2O.d.ts} +54 -13
- package/dist/{PackageRegistry-pHtKythi.d.cts → PackageRegistry-u2zdfFaj.d.cts} +54 -13
- package/dist/{Parselet-DEdF9I7n.d.ts → Parselet-BaySkMV3.d.ts} +37 -5
- package/dist/{Parselet-Cu0bLSis.d.cts → Parselet-DuI1Pjiq.d.cts} +37 -5
- package/dist/{ScopeManager-CKfQtl7l.d.cts → ScopeManager-B6GzdhVG.d.cts} +75 -6
- package/dist/{ScopeManager-B5k0nk05.d.ts → ScopeManager-udv4Twwq.d.ts} +75 -6
- package/dist/{VMCheckpoints-BmDwIXgK.d.ts → VMCheckpoints-BiaIlOOY.d.ts} +4 -4
- package/dist/{VMCheckpoints-CqFuLwrT.d.cts → VMCheckpoints-DwLjivM7.d.cts} +4 -4
- package/dist/{Value-KtAQNHur.d.cts → Value-CXJqDH9J.d.cts} +5 -2
- package/dist/{Value-KtAQNHur.d.ts → Value-CXJqDH9J.d.ts} +5 -2
- package/dist/{chunk-CSG5XVNJ.cjs → chunk-3AFRJYP4.cjs} +556 -414
- package/dist/chunk-3AFRJYP4.cjs.map +1 -0
- package/dist/chunk-3BOK2LDK.js +17 -0
- package/dist/chunk-3BOK2LDK.js.map +1 -0
- package/dist/{chunk-6RDSQURE.js → chunk-3D7V24DG.js} +57 -6
- package/dist/chunk-3D7V24DG.js.map +1 -0
- package/dist/{chunk-MEOHSQEH.js → chunk-3LAEG75D.js} +47 -15
- package/dist/chunk-3LAEG75D.js.map +1 -0
- package/dist/{chunk-P75775WC.js → chunk-3VNGREMU.js} +118 -41
- package/dist/chunk-3VNGREMU.js.map +1 -0
- package/dist/{chunk-4AVD7NZW.cjs → chunk-4B2CNWQU.cjs} +13 -7
- package/dist/chunk-4B2CNWQU.cjs.map +1 -0
- package/dist/{chunk-6GCKCWLB.js → chunk-526PMQOA.js} +126 -69
- package/dist/chunk-526PMQOA.js.map +1 -0
- package/dist/{chunk-2CS6OMZK.js → chunk-5F4C26RU.js} +8 -5
- package/dist/chunk-5F4C26RU.js.map +1 -0
- package/dist/{chunk-M4N5GA5Z.cjs → chunk-5KMIY374.cjs} +57 -6
- package/dist/chunk-5KMIY374.cjs.map +1 -0
- package/dist/{chunk-R24DI24X.cjs → chunk-5LI5EPGJ.cjs} +6 -5
- package/dist/chunk-5LI5EPGJ.cjs.map +1 -0
- package/dist/{chunk-BAMZT3GL.js → chunk-5WVP4YHP.js} +939 -134
- package/dist/chunk-5WVP4YHP.js.map +1 -0
- package/dist/{chunk-MZOVP3WI.cjs → chunk-6KFYJ6TD.cjs} +3 -2
- package/dist/chunk-6KFYJ6TD.cjs.map +1 -0
- package/dist/{chunk-BXKJLM5R.cjs → chunk-6WFMPTGB.cjs} +972 -636
- package/dist/chunk-6WFMPTGB.cjs.map +1 -0
- package/dist/{chunk-2MV4HBKC.cjs → chunk-A2N2GFCG.cjs} +10 -10
- package/dist/{chunk-2MV4HBKC.cjs.map → chunk-A2N2GFCG.cjs.map} +1 -1
- package/dist/chunk-ADBA3XBE.cjs +20 -0
- package/dist/chunk-ADBA3XBE.cjs.map +1 -0
- package/dist/{chunk-MBNQVDVC.cjs → chunk-AMOJYLG2.cjs} +16 -10
- package/dist/chunk-AMOJYLG2.cjs.map +1 -0
- package/dist/{chunk-CLVQBF5C.cjs → chunk-AQNHHJMJ.cjs} +5 -5
- package/dist/{chunk-CLVQBF5C.cjs.map → chunk-AQNHHJMJ.cjs.map} +1 -1
- package/dist/{chunk-AHCLWAM5.cjs → chunk-BHTNFEGZ.cjs} +13 -13
- package/dist/chunk-BHTNFEGZ.cjs.map +1 -0
- package/dist/{chunk-35IWNZPJ.cjs → chunk-E4NUZXVK.cjs} +58 -27
- package/dist/chunk-E4NUZXVK.cjs.map +1 -0
- package/dist/{chunk-P33SBYGG.js → chunk-GLG5ZMX7.js} +54 -23
- package/dist/chunk-GLG5ZMX7.js.map +1 -0
- package/dist/{chunk-BVAR5J2F.js → chunk-GQCOSXMG.js} +117 -6
- package/dist/chunk-GQCOSXMG.js.map +1 -0
- package/dist/{chunk-QDMPOAI7.cjs → chunk-HDP7VK3C.cjs} +1118 -313
- package/dist/chunk-HDP7VK3C.cjs.map +1 -0
- package/dist/{chunk-GVWHCL2N.js → chunk-HMOISHXR.js} +217 -75
- package/dist/chunk-HMOISHXR.js.map +1 -0
- package/dist/{chunk-MTX2KVU7.cjs → chunk-HVQFNJKE.cjs} +122 -90
- package/dist/chunk-HVQFNJKE.cjs.map +1 -0
- package/dist/{chunk-OC6LTXYS.cjs → chunk-JMXUNXQS.cjs} +126 -7
- package/dist/chunk-JMXUNXQS.cjs.map +1 -0
- package/dist/{chunk-WWOLFXTX.cjs → chunk-KBSXGXPM.cjs} +10 -7
- package/dist/chunk-KBSXGXPM.cjs.map +1 -0
- package/dist/{chunk-YU2CUNFO.js → chunk-KV7UW6T6.js} +11 -5
- package/dist/chunk-KV7UW6T6.js.map +1 -0
- package/dist/{chunk-CCJPSHGI.cjs → chunk-N7FNVFXW.cjs} +4 -12
- package/dist/chunk-N7FNVFXW.cjs.map +1 -0
- package/dist/{chunk-CYFK5SY2.js → chunk-O3ANBHSA.js} +169 -39
- package/dist/chunk-O3ANBHSA.js.map +1 -0
- package/dist/{chunk-NZFKROS7.cjs → chunk-PA4VC73I.cjs} +130 -72
- package/dist/chunk-PA4VC73I.cjs.map +1 -0
- package/dist/{chunk-NJTXJ5AG.cjs → chunk-PFUESQTW.cjs} +18 -18
- package/dist/{chunk-NJTXJ5AG.cjs.map → chunk-PFUESQTW.cjs.map} +1 -1
- package/dist/{chunk-SFQWJMKT.js → chunk-QY25VWBF.js} +51 -12
- package/dist/chunk-QY25VWBF.js.map +1 -0
- package/dist/{chunk-WC5FFSHB.js → chunk-R3PY4G7J.js} +3 -3
- package/dist/{chunk-WC5FFSHB.js.map → chunk-R3PY4G7J.js.map} +1 -1
- package/dist/{chunk-M5E34VG5.js → chunk-SBUFNJVE.js} +3 -3
- package/dist/{chunk-M5E34VG5.js.map → chunk-SBUFNJVE.js.map} +1 -1
- package/dist/{chunk-UKPGSAZW.js → chunk-TMA4RCEN.js} +3 -3
- package/dist/{chunk-UKPGSAZW.js.map → chunk-TMA4RCEN.js.map} +1 -1
- package/dist/{chunk-V6HA6JJZ.cjs → chunk-U5VAY32K.cjs} +215 -138
- package/dist/chunk-U5VAY32K.cjs.map +1 -0
- package/dist/{chunk-DUEZFTK2.js → chunk-UKQHXPGW.js} +4 -12
- package/dist/chunk-UKQHXPGW.js.map +1 -0
- package/dist/{chunk-4CVLFLOB.cjs → chunk-UM6BVY2S.cjs} +201 -69
- package/dist/chunk-UM6BVY2S.cjs.map +1 -0
- package/dist/{chunk-75JP4BZX.js → chunk-UQ3UIZJC.js} +3 -2
- package/dist/chunk-UQ3UIZJC.js.map +1 -0
- package/dist/{chunk-PCPX42KL.js → chunk-V4XG5QF6.js} +16 -10
- package/dist/chunk-V4XG5QF6.js.map +1 -0
- package/dist/{chunk-GPPLSM2Z.js → chunk-VB37OC6I.js} +6 -5
- package/dist/chunk-VB37OC6I.js.map +1 -0
- package/dist/{chunk-MVBEL57Q.js → chunk-Y7FT4IQT.js} +500 -170
- package/dist/chunk-Y7FT4IQT.js.map +1 -0
- package/dist/{chunk-OFXOTECC.js → chunk-YPPPYLSR.js} +7 -7
- package/dist/chunk-YPPPYLSR.js.map +1 -0
- package/dist/{chunk-M4F66R4O.cjs → chunk-YWQ6V4ZN.cjs} +121 -82
- package/dist/chunk-YWQ6V4ZN.cjs.map +1 -0
- package/dist/constants.cjs +5 -5
- package/dist/constants.d.cts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +2 -2
- package/dist/engine.cjs +31 -31
- package/dist/engine.d.cts +11 -11
- package/dist/engine.d.ts +11 -11
- package/dist/engine.js +21 -21
- package/dist/errors.cjs +19 -19
- package/dist/errors.d.cts +2 -2
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +1 -1
- package/dist/format.cjs +29 -22
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.cts +1 -2
- package/dist/format.d.ts +1 -2
- package/dist/format.js +23 -16
- package/dist/format.js.map +1 -1
- package/dist/index.cjs +31 -31
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +22 -22
- package/dist/language.cjs +8 -8
- package/dist/language.cjs.map +1 -1
- package/dist/language.d.cts +10 -10
- package/dist/language.d.ts +10 -10
- package/dist/language.js +6 -6
- package/dist/language.js.map +1 -1
- package/dist/lexer.cjs +18 -18
- package/dist/lexer.d.cts +2 -2
- package/dist/lexer.d.ts +2 -2
- package/dist/lexer.js +6 -6
- package/dist/normalizer.cjs +12 -12
- package/dist/normalizer.js +6 -6
- package/dist/packages.cjs +39 -39
- package/dist/packages.d.cts +9 -9
- package/dist/packages.d.ts +9 -9
- package/dist/packages.js +16 -16
- package/dist/parser.cjs +15 -15
- package/dist/parser.d.cts +23 -9
- package/dist/parser.d.ts +23 -9
- package/dist/parser.js +5 -5
- package/dist/{pipeline-CrsRPmsv.d.ts → pipeline-B6k5lCB7.d.ts} +1 -1
- package/dist/{pipeline-DPUQt7yL.d.cts → pipeline-BEb3hujr.d.cts} +1 -1
- package/dist/resolvers.d.cts +2 -2
- package/dist/resolvers.d.ts +2 -2
- package/dist/uom.cjs +17 -17
- package/dist/uom.d.cts +2 -2
- package/dist/uom.d.ts +2 -2
- package/dist/uom.js +7 -7
- package/dist/utilities.cjs +8 -4
- package/dist/utilities.d.cts +19 -3
- package/dist/utilities.d.ts +19 -3
- package/dist/utilities.js +2 -2
- package/dist/vm.cjs +43 -31
- package/dist/vm.d.cts +105 -9
- package/dist/vm.d.ts +105 -9
- package/dist/vm.js +9 -9
- package/package.json +1 -1
- package/dist/chunk-2CS6OMZK.js.map +0 -1
- package/dist/chunk-35IWNZPJ.cjs.map +0 -1
- package/dist/chunk-4AVD7NZW.cjs.map +0 -1
- package/dist/chunk-4CVLFLOB.cjs.map +0 -1
- package/dist/chunk-6GCKCWLB.js.map +0 -1
- package/dist/chunk-6RDSQURE.js.map +0 -1
- package/dist/chunk-75JP4BZX.js.map +0 -1
- package/dist/chunk-AHCLWAM5.cjs.map +0 -1
- package/dist/chunk-BAMZT3GL.js.map +0 -1
- package/dist/chunk-BVAR5J2F.js.map +0 -1
- package/dist/chunk-BXKJLM5R.cjs.map +0 -1
- package/dist/chunk-CCJPSHGI.cjs.map +0 -1
- package/dist/chunk-CSG5XVNJ.cjs.map +0 -1
- package/dist/chunk-CYFK5SY2.js.map +0 -1
- package/dist/chunk-DUEZFTK2.js.map +0 -1
- package/dist/chunk-GPPLSM2Z.js.map +0 -1
- package/dist/chunk-GVWHCL2N.js.map +0 -1
- package/dist/chunk-M4F66R4O.cjs.map +0 -1
- package/dist/chunk-M4N5GA5Z.cjs.map +0 -1
- package/dist/chunk-MBNQVDVC.cjs.map +0 -1
- package/dist/chunk-MEOHSQEH.js.map +0 -1
- package/dist/chunk-MTX2KVU7.cjs.map +0 -1
- package/dist/chunk-MVBEL57Q.js.map +0 -1
- package/dist/chunk-MZOVP3WI.cjs.map +0 -1
- package/dist/chunk-N5YFR4UP.cjs +0 -10
- package/dist/chunk-N5YFR4UP.cjs.map +0 -1
- package/dist/chunk-NZFKROS7.cjs.map +0 -1
- package/dist/chunk-OC6LTXYS.cjs.map +0 -1
- package/dist/chunk-OFXOTECC.js.map +0 -1
- package/dist/chunk-P33SBYGG.js.map +0 -1
- package/dist/chunk-P75775WC.js.map +0 -1
- package/dist/chunk-PCPX42KL.js.map +0 -1
- package/dist/chunk-PFSDGGYL.js +0 -8
- package/dist/chunk-PFSDGGYL.js.map +0 -1
- package/dist/chunk-QDMPOAI7.cjs.map +0 -1
- package/dist/chunk-R24DI24X.cjs.map +0 -1
- package/dist/chunk-SFQWJMKT.js.map +0 -1
- package/dist/chunk-V6HA6JJZ.cjs.map +0 -1
- package/dist/chunk-WWOLFXTX.cjs.map +0 -1
- package/dist/chunk-YU2CUNFO.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkVEKIWQ3F_cjs = require('./chunk-VEKIWQ3F.cjs');
|
|
4
|
-
var
|
|
4
|
+
var chunk5KMIY374_cjs = require('./chunk-5KMIY374.cjs');
|
|
5
5
|
|
|
6
6
|
// src/uom/Iso4217.ts
|
|
7
7
|
var ISO_4217_CODES = /* @__PURE__ */ new Set([
|
|
@@ -225,7 +225,7 @@ var _CurrencyExchangeService = class _CurrencyExchangeService {
|
|
|
225
225
|
);
|
|
226
226
|
try {
|
|
227
227
|
const response = await fetch(`https://api.frankfurter.dev/v2/rates?base=${fromUpper}`, { signal: fetchSignal });
|
|
228
|
-
if (!response.ok) throw
|
|
228
|
+
if (!response.ok) throw chunk5KMIY374_cjs.ErrorFactory.external(CurrencyErrorCodes.API_ERROR, `Currency API returned ${response.status}`, { status: response.status });
|
|
229
229
|
const data = await response.json();
|
|
230
230
|
const rates = Array.isArray(data) ? Object.fromEntries(
|
|
231
231
|
data.filter((entry) => {
|
|
@@ -234,7 +234,7 @@ var _CurrencyExchangeService = class _CurrencyExchangeService {
|
|
|
234
234
|
return typeof candidate.quote === "string" && typeof candidate.rate === "number";
|
|
235
235
|
}).map((entry) => [entry.quote.toUpperCase(), entry.rate])
|
|
236
236
|
) : data.rates ?? {};
|
|
237
|
-
if (rates[toUpper] === void 0) throw
|
|
237
|
+
if (rates[toUpper] === void 0) throw chunk5KMIY374_cjs.ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });
|
|
238
238
|
this.baseTables.set(fromUpper, {
|
|
239
239
|
fetchedAt: Date.now(),
|
|
240
240
|
rates: { ...rates, [fromUpper]: 1 }
|
|
@@ -269,22 +269,22 @@ var _CurrencyExchangeService = class _CurrencyExchangeService {
|
|
|
269
269
|
const data = await this.fetchCoinGeckoPrices([fromId, toId], "usd", fetchSignal);
|
|
270
270
|
const fromUsd = data[fromId]?.usd;
|
|
271
271
|
const toUsd = data[toId]?.usd;
|
|
272
|
-
if (typeof fromUsd !== "number") throw
|
|
273
|
-
if (typeof toUsd !== "number") throw
|
|
272
|
+
if (typeof fromUsd !== "number") throw chunk5KMIY374_cjs.ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${fromUpper}`, { code: fromUpper });
|
|
273
|
+
if (typeof toUsd !== "number") throw chunk5KMIY374_cjs.ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });
|
|
274
274
|
rate = fromUsd / toUsd;
|
|
275
275
|
} else if (fromIsCrypto) {
|
|
276
276
|
const fromId = _CurrencyExchangeService.CRYPTO_IDS[fromUpper];
|
|
277
277
|
const vs = toUpper.toLowerCase();
|
|
278
278
|
const data = await this.fetchCoinGeckoPrices([fromId], vs, fetchSignal);
|
|
279
279
|
const value = data[fromId]?.[vs];
|
|
280
|
-
if (typeof value !== "number") throw
|
|
280
|
+
if (typeof value !== "number") throw chunk5KMIY374_cjs.ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });
|
|
281
281
|
rate = value;
|
|
282
282
|
} else {
|
|
283
283
|
const toId = _CurrencyExchangeService.CRYPTO_IDS[toUpper];
|
|
284
284
|
const vs = fromUpper.toLowerCase();
|
|
285
285
|
const data = await this.fetchCoinGeckoPrices([toId], vs, fetchSignal);
|
|
286
286
|
const priceOfToInFrom = data[toId]?.[vs];
|
|
287
|
-
if (typeof priceOfToInFrom !== "number") throw
|
|
287
|
+
if (typeof priceOfToInFrom !== "number") throw chunk5KMIY374_cjs.ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${fromUpper}`, { code: fromUpper });
|
|
288
288
|
rate = 1 / priceOfToInFrom;
|
|
289
289
|
}
|
|
290
290
|
this.baseTables.set(fromUpper, {
|
|
@@ -299,7 +299,7 @@ var _CurrencyExchangeService = class _CurrencyExchangeService {
|
|
|
299
299
|
async fetchCoinGeckoPrices(ids, vsCurrency, signal) {
|
|
300
300
|
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids.join(",")}&vs_currencies=${vsCurrency}`;
|
|
301
301
|
const response = await fetch(url, { signal });
|
|
302
|
-
if (!response.ok) throw
|
|
302
|
+
if (!response.ok) throw chunk5KMIY374_cjs.ErrorFactory.external(CurrencyErrorCodes.CRYPTO_API_ERROR, `Crypto price API returned ${response.status}`, { status: response.status });
|
|
303
303
|
return response.json();
|
|
304
304
|
}
|
|
305
305
|
/**
|
|
@@ -455,5 +455,5 @@ var sharedCurrencyExchange = currencyExchangeService;
|
|
|
455
455
|
exports.CurrencyExchangeService = CurrencyExchangeService;
|
|
456
456
|
exports.currencyExchangeService = currencyExchangeService;
|
|
457
457
|
exports.sharedCurrencyExchange = sharedCurrencyExchange;
|
|
458
|
-
//# sourceMappingURL=chunk-
|
|
459
|
-
//# sourceMappingURL=chunk-
|
|
458
|
+
//# sourceMappingURL=chunk-A2N2GFCG.cjs.map
|
|
459
|
+
//# sourceMappingURL=chunk-A2N2GFCG.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/uom/Iso4217.ts","../src/uom/CurrencyExchange.ts"],"names":["createTimeoutSignal","ErrorFactory"],"mappings":";;;;;;AAyBO,IAAM,cAAA,uBAA0C,GAAA,CAAI;AAAA,EAC1D,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC/D,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC/D,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACnC,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC/D,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACrB,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACrB,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACrB,KAAA;AAAA,EAAO,KAAA;AAAA,EACP,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACjD,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACrB,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACnC,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACd,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACxD,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACnC,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC/D,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC5B,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACnC,KAAA;AAAA,EACA,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC1C,KAAA;AAAA,EACA,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACrB,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC/D,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC5B,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACxD,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACjD,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EACrB,KAAA;AAAA,EACA,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,KAAA;AAAA,EAC5B,KAAA;AAAA,EACA,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO;AACf,CAAC,CAAA;AAOM,SAAS,UAAU,IAAA,EAAuB;AAChD,EAAA,OAAO,cAAA,CAAe,GAAA,CAAI,IAAA,CAAK,WAAA,EAAa,CAAA;AAC7C;;;ACjDO,IAAM,kBAAA,GAAqB;AAAA;AAAA,EAEhC,SAAA,EAAW,oBAAA;AAAA;AAAA,EAEX,YAAA,EAAc,uBAAA;AAAA;AAAA,EAEd,gBAAA,EAAkB;AACpB,CAAA;AAcO,IAAM,wBAAA,GAAN,MAAM,wBAAA,CAAwB;AAAA,EAqCnC,WAAA,GAAc;AA3Bd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,IAAA,CAAQ,UAAA,uBAAoF,GAAA,EAAI;AAAA,EA2BjF;AAAA,EAJP,aAAa,IAAA,EAAuB;AAC1C,IAAA,OAAO,IAAA,CAAK,WAAA,EAAY,IAAK,wBAAA,CAAwB,UAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BA,MAAM,OAAA,CAAQ,IAAA,EAAc,EAAA,EAAY,MAAA,EAAuC;AAC7E,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,EAAY;AACnC,IAAA,MAAM,OAAA,GAAU,GAAG,WAAA,EAAY;AAE/B,IAAA,IAAI,KAAK,YAAA,CAAa,SAAS,KAAK,IAAA,CAAK,YAAA,CAAa,OAAO,CAAA,EAAG;AAC9D,MAAA,OAAO,IAAA,CAAK,aAAA,CAAc,SAAA,EAAW,OAAA,EAAS,MAAM,CAAA;AAAA,IACtD;AAIA,IAAA,MAAM,EAAE,MAAA,EAAQ,WAAA,EAAa,OAAA,EAAQ,GAAIA,qCAAA;AAAA,MACvC,MAAA;AAAA,MACA,wBAAA,CAAwB,gBAAA;AAAA,MACxB;AAAA,KACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,CAAA,0CAAA,EAA6C,SAAS,CAAA,CAAA,EAAI,EAAE,MAAA,EAAQ,WAAA,EAAa,CAAA;AAC9G,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAMC,8BAAA,CAAa,SAAS,kBAAA,CAAmB,SAAA,EAAW,CAAA,sBAAA,EAAyB,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,EAAE,MAAA,EAAQ,QAAA,CAAS,QAAQ,CAAA;AACnJ,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAQjC,MAAA,MAAM,KAAA,GAAgC,KAAA,CAAM,OAAA,CAAQ,IAAI,IACpD,MAAA,CAAO,WAAA;AAAA,QACL,IAAA,CACG,MAAA,CAAO,CAAC,KAAA,KAA6D;AACpE,UAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,MAAM,OAAO,KAAA;AACxD,UAAA,MAAM,SAAA,GAAY,KAAA;AAClB,UAAA,OAAO,OAAO,SAAA,CAAU,KAAA,KAAU,QAAA,IAAY,OAAO,UAAU,IAAA,KAAS,QAAA;AAAA,QAC1E,CAAC,CAAA,CACA,GAAA,CAAI,CAAC,KAAA,KAA2C,CAAC,KAAA,CAAM,KAAA,CAAM,WAAA,EAAY,EAAG,KAAA,CAAM,IAAI,CAAC;AAAA,OAC5F,GACC,IAAA,CAAK,KAAA,IAAS,EAAC;AACpB,MAAA,IAAI,KAAA,CAAM,OAAO,CAAA,KAAM,KAAA,CAAA,QAAiBA,8BAAA,CAAa,UAAA,CAAW,kBAAA,CAAmB,YAAA,EAAc,qBAAqB,OAAO,CAAA,CAAA,EAAI,EAAE,IAAA,EAAM,SAAS,CAAA;AAMlJ,MAAA,IAAA,CAAK,UAAA,CAAW,IAAI,SAAA,EAAW;AAAA,QAC7B,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,QACpB,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,CAAA;AAAE,OACnC,CAAA;AAED,MAAA,OAAO,MAAM,OAAO,CAAA;AAAA,IACtB,CAAA,SAAE;AACA,MAAA,OAAA,EAAQ;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAc,aAAA,CAAc,SAAA,EAAmB,OAAA,EAAiB,MAAA,EAAuC;AACrG,IAAA,MAAM,EAAE,MAAA,EAAQ,WAAA,EAAa,OAAA,EAAQ,GAAID,qCAAA;AAAA,MACvC,MAAA;AAAA,MACA,wBAAA,CAAwB,gBAAA;AAAA,MACxB;AAAA,KACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,YAAA,CAAa,SAAS,CAAA;AAChD,MAAA,MAAM,UAAA,GAAa,IAAA,CAAK,YAAA,CAAa,OAAO,CAAA;AAC5C,MAAA,IAAI,IAAA;AAEJ,MAAA,IAAI,gBAAgB,UAAA,EAAY;AAC9B,QAAA,MAAM,MAAA,GAAS,wBAAA,CAAwB,UAAA,CAAW,SAAS,CAAA;AAC3D,QAAA,MAAM,IAAA,GAAO,wBAAA,CAAwB,UAAA,CAAW,OAAO,CAAA;AACvD,QAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,oBAAA,CAAqB,CAAC,MAAA,EAAQ,IAAI,CAAA,EAAG,KAAA,EAAO,WAAW,CAAA;AAC/E,QAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAM,CAAA,EAAG,GAAA;AAC9B,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,GAAA;AAC1B,QAAA,IAAI,OAAO,OAAA,KAAY,QAAA,EAAU,MAAMC,+BAAa,UAAA,CAAW,kBAAA,CAAmB,YAAA,EAAc,CAAA,kBAAA,EAAqB,SAAS,CAAA,CAAA,EAAI,EAAE,IAAA,EAAM,WAAW,CAAA;AACrJ,QAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,MAAMA,+BAAa,UAAA,CAAW,kBAAA,CAAmB,YAAA,EAAc,CAAA,kBAAA,EAAqB,OAAO,CAAA,CAAA,EAAI,EAAE,IAAA,EAAM,SAAS,CAAA;AAC/I,QAAA,IAAA,GAAO,OAAA,GAAU,KAAA;AAAA,MACnB,WAAW,YAAA,EAAc;AACvB,QAAA,MAAM,MAAA,GAAS,wBAAA,CAAwB,UAAA,CAAW,SAAS,CAAA;AAC3D,QAAA,MAAM,EAAA,GAAK,QAAQ,WAAA,EAAY;AAC/B,QAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,oBAAA,CAAqB,CAAC,MAAM,CAAA,EAAG,IAAI,WAAW,CAAA;AACtE,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,CAAA,GAAI,EAAE,CAAA;AAC/B,QAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,MAAMA,+BAAa,UAAA,CAAW,kBAAA,CAAmB,YAAA,EAAc,CAAA,kBAAA,EAAqB,OAAO,CAAA,CAAA,EAAI,EAAE,IAAA,EAAM,SAAS,CAAA;AAC/I,QAAA,IAAA,GAAO,KAAA;AAAA,MACT,CAAA,MAAO;AACL,QAAA,MAAM,IAAA,GAAO,wBAAA,CAAwB,UAAA,CAAW,OAAO,CAAA;AACvD,QAAA,MAAM,EAAA,GAAK,UAAU,WAAA,EAAY;AACjC,QAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,oBAAA,CAAqB,CAAC,IAAI,CAAA,EAAG,IAAI,WAAW,CAAA;AACpE,QAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,IAAI,CAAA,GAAI,EAAE,CAAA;AACvC,QAAA,IAAI,OAAO,eAAA,KAAoB,QAAA,EAAU,MAAMA,+BAAa,UAAA,CAAW,kBAAA,CAAmB,YAAA,EAAc,CAAA,kBAAA,EAAqB,SAAS,CAAA,CAAA,EAAI,EAAE,IAAA,EAAM,WAAW,CAAA;AAC7J,QAAA,IAAA,GAAO,CAAA,GAAI,eAAA;AAAA,MACb;AAKA,MAAA,IAAA,CAAK,UAAA,CAAW,IAAI,SAAA,EAAW;AAAA,QAC7B,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,QACpB,KAAA,EAAO,EAAE,CAAC,SAAS,GAAG,CAAA,EAAG,CAAC,OAAO,GAAG,IAAA;AAAK,OAC1C,CAAA;AAED,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,OAAA,EAAQ;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAc,oBAAA,CAAqB,GAAA,EAAe,UAAA,EAAoB,MAAA,EAAsE;AAC1I,IAAA,MAAM,MAAM,CAAA,kDAAA,EAAqD,GAAA,CAAI,KAAK,GAAG,CAAC,kBAAkB,UAAU,CAAA,CAAA;AAC1G,IAAA,MAAM,WAAW,MAAM,KAAA,CAAM,GAAA,EAAK,EAAE,QAAQ,CAAA;AAC5C,IAAA,IAAI,CAAC,QAAA,CAAS,EAAA,EAAI,MAAMA,8BAAA,CAAa,SAAS,kBAAA,CAAmB,gBAAA,EAAkB,CAAA,0BAAA,EAA6B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,EAAE,MAAA,EAAQ,QAAA,CAAS,QAAQ,CAAA;AAC9J,IAAA,OAAO,SAAS,IAAA,EAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,UAAA,CAAW,MAAc,KAAA,EAAqC;AAC5D,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,EAAY;AACnC,IAAA,IAAA,CAAK,UAAA,CAAW,IAAI,SAAA,EAAW;AAAA,MAC7B,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,MACpB,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,GAAG,CAAA;AAAE,KACnC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,WAAA,CAAY,MAAc,EAAA,EAA2B;AACnD,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,EAAY;AACnC,IAAA,MAAM,OAAA,GAAU,GAAG,WAAA,EAAY;AAC/B,IAAA,IAAI,cAAc,OAAA,EAAS;AACzB,MAAA,OAAO,CAAA;AAAA,IACT;AACA,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,UAAA,CAAW,MAAA,EAAO,EAAG;AAC5C,MAAA,IAAI,GAAA,GAAM,KAAA,CAAM,SAAA,GAAY,wBAAA,CAAwB,iBAAA,EAAmB;AACvE,MAAA,MAAM,QAAA,GAAW,KAAA,CAAM,KAAA,CAAM,SAAS,CAAA;AACtC,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAClC,MAAA,IAAI,YAAY,MAAA,EAAQ;AACtB,QAAA,OAAO,MAAA,GAAS,QAAA;AAAA,MAClB;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,OAAA,CAAQ,KAAA,EAAe,IAAA,EAAc,EAAA,EAA6B;AACtE,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,OAAA,CAAQ,MAAM,EAAE,CAAA;AACxC,IAAA,OAAO,KAAA,GAAQ,IAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAA,GAA6C;AAC3C,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,WAAmC,EAAC;AAC1C,IAAA,IAAI,GAAA,GAAM,KAAA;AACV,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,CAAA,IAAK,KAAK,UAAA,EAAY;AAC3C,MAAA,IAAI,GAAA,GAAM,KAAA,CAAM,SAAA,GAAY,wBAAA,CAAwB,iBAAA,EAAmB;AACvE,MAAA,KAAA,MAAW,CAAC,MAAM,IAAI,CAAA,IAAK,OAAO,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,EAAG;AACtD,QAAA,QAAA,CAAS,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,EAAE,CAAA,GAAI,IAAA;AAC9B,QAAA,GAAA,GAAM,IAAA;AAAA,MACR;AAAA,IACF;AACA,IAAA,OAAO,MAAM,QAAA,GAAW,IAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,aAAY,KAAM,IAAA;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAA,CAAY,KAAA,EAAe,IAAA,EAAc,EAAA,EAA2B;AAClE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,IAAA,EAAM,EAAE,CAAA;AACtC,IAAA,IAAI,IAAA,KAAS,MAAM,OAAO,IAAA;AAC1B,IAAA,OAAO,KAAA,GAAQ,IAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,WAAW,IAAA,EAAuB;AAChC,IAAA,OAAO,SAAA,CAAU,IAAI,CAAA,IAAK,IAAA,CAAK,aAAa,IAAI,CAAA;AAAA,EAClD;AACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA/Sa,wBAAA,CAiBa,iBAAA,GAAoB,KAAK,EAAA,GAAK,GAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAjB3C,wBAAA,CA4Ba,UAAA,GAAqC;AAAA,EAC3D,GAAA,EAAK,SAAA;AAAA,EAAW,GAAA,EAAK,UAAA;AAAA,EAAY,GAAA,EAAK,QAAA;AAAA,EAAU,GAAA,EAAK,QAAA;AAAA,EACrD,GAAA,EAAK,SAAA;AAAA,EAAW,IAAA,EAAM,UAAA;AAAA,EAAY,GAAA,EAAK;AACzC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA/BW,wBAAA,CAkDa,gBAAA,GAAmB,GAAA;AAlDtC,IAAM,uBAAA,GAAN;AAsTA,IAAM,uBAAA,GAA0B,IAAI,uBAAA;AAIpC,IAAM,sBAAA,GAAyB","file":"chunk-2MV4HBKC.cjs","sourcesContent":["/**\n * Every active ISO 4217 currency code.\n *\n * This exists because `CurrencyExchange.isCurrency()` used to hold a\n * hand-written list of forty-six codes, and a code missing from it did not\n * error: `$100 in UAH` returned the original hundred dollars, unconverted, as\n * though the rate were 1. A wrong answer, not a missing feature.\n *\n * The list is the ISO 4217 active set, so \"is this a currency\" is answered by\n * the standard rather than by whichever codes happened to get added. Whether a\n * *rate* is available for one is a separate question, answered at resolution\n * time by whatever the exchange provider supports; conflating the two is what\n * produced the silent failure.\n *\n * Deliberately excluded:\n * - The historical/withdrawn codes (DEM, FRF, and the rest). They are not\n * active, no provider quotes them, and recognising them would only move the\n * failure later.\n * - The X-series non-currencies: XAU/XAG/XPT/XPD (metals), XDR (IMF special\n * drawing rights), XTS (reserved for testing) and XXX (explicitly \"no\n * currency\"). XXX in particular must never resolve.\n *\n * Cryptocurrencies are not here either. They are not ISO 4217 and are tracked\n * separately in `CurrencyExchangeService.CRYPTO_IDS`.\n */\nexport const ISO_4217_CODES: ReadonlySet<string> = new Set([\n\t\"AED\", \"AFN\", \"ALL\", \"AMD\", \"ANG\", \"AOA\", \"ARS\", \"AUD\", \"AWG\", \"AZN\",\n\t\"BAM\", \"BBD\", \"BDT\", \"BGN\", \"BHD\", \"BIF\", \"BMD\", \"BND\", \"BOB\", \"BOV\",\n\t\"BRL\", \"BSD\", \"BTN\", \"BWP\", \"BYN\", \"BZD\",\n\t\"CAD\", \"CDF\", \"CHE\", \"CHF\", \"CHW\", \"CLF\", \"CLP\", \"CNY\", \"COP\", \"COU\",\n\t\"CRC\", \"CUP\", \"CVE\", \"CZK\",\n\t\"DJF\", \"DKK\", \"DOP\", \"DZD\",\n\t\"EGP\", \"ERN\", \"ETB\", \"EUR\",\n\t\"FJD\", \"FKP\",\n\t\"GBP\", \"GEL\", \"GHS\", \"GIP\", \"GMD\", \"GNF\", \"GTQ\", \"GYD\",\n\t\"HKD\", \"HNL\", \"HTG\", \"HUF\",\n\t\"IDR\", \"ILS\", \"INR\", \"IQD\", \"IRR\", \"ISK\",\n\t\"JMD\", \"JOD\", \"JPY\",\n\t\"KES\", \"KGS\", \"KHR\", \"KMF\", \"KPW\", \"KRW\", \"KWD\", \"KYD\", \"KZT\",\n\t\"LAK\", \"LBP\", \"LKR\", \"LRD\", \"LSL\", \"LYD\",\n\t\"MAD\", \"MDL\", \"MGA\", \"MKD\", \"MMK\", \"MNT\", \"MOP\", \"MRU\", \"MUR\", \"MVR\",\n\t\"MWK\", \"MXN\", \"MXV\", \"MYR\", \"MZN\",\n\t\"NAD\", \"NGN\", \"NIO\", \"NOK\", \"NPR\", \"NZD\",\n\t\"OMR\",\n\t\"PAB\", \"PEN\", \"PGK\", \"PHP\", \"PKR\", \"PLN\", \"PYG\",\n\t\"QAR\",\n\t\"RON\", \"RSD\", \"RUB\", \"RWF\",\n\t\"SAR\", \"SBD\", \"SCR\", \"SDG\", \"SEK\", \"SGD\", \"SHP\", \"SLE\", \"SOS\", \"SRD\",\n\t\"SSP\", \"STN\", \"SVC\", \"SYP\", \"SZL\",\n\t\"THB\", \"TJS\", \"TMT\", \"TND\", \"TOP\", \"TRY\", \"TTD\", \"TWD\", \"TZS\",\n\t\"UAH\", \"UGX\", \"USD\", \"USN\", \"UYI\", \"UYU\", \"UYW\", \"UZS\",\n\t\"VED\", \"VES\", \"VND\", \"VUV\",\n\t\"WST\",\n\t\"XAF\", \"XCD\", \"XCG\", \"XOF\", \"XPF\",\n\t\"YER\",\n\t\"ZAR\", \"ZMW\", \"ZWG\",\n]);\n\n/**\n * Whether `code` is an active ISO 4217 currency code.\n *\n * @param code - A three-letter code, in any case.\n */\nexport function isIso4217(code: string): boolean {\n\treturn ISO_4217_CODES.has(code.toUpperCase());\n}\n","/**\n * Production-grade currency exchange service with scalable architecture\n * Integrates with DataQueryService for worker-based execution\n */\n\nimport { isIso4217 } from \"@solve-js/uom/Iso4217\";\nimport { createTimeoutSignal } from \"@solve-js/utilities/TimeoutSignal\";\nimport { ErrorFactory } from \"@solve-js/errors/UnifiedErrorFramework\";\n\n/**\n * Error codes for this service. Co-located rather than unioned into\n * `errors/ErrorCode.ts`'s core catalog (that catalog is scoped to the\n * parser/VM/engine/errors/config/lexer layers, not yet the ~17 domain\n * packages. See that file's module doc for the intended per-package\n * pattern this follows).\n */\nexport const CurrencyErrorCodes = {\n /** Frankfurter's rates endpoint returned a non-OK HTTP status. */\n API_ERROR: \"CURRENCY_API_ERROR\",\n /** A requested currency/crypto code isn't in the fetched rate table, an unrecognized code, not an API failure. */\n UNKNOWN_CODE: \"UNKNOWN_CURRENCY_CODE\",\n /** CoinGecko's simple-price endpoint returned a non-OK HTTP status. */\n CRYPTO_API_ERROR: \"CRYPTO_PRICE_API_ERROR\",\n} as const;\n\n// ============================================================================\n// CURRENCY EXCHANGE SERVICE\n// ============================================================================\n\n/**\n * Caches exchange rates fetched from an external source.\n *\n * Rates are global market data rather than per-engine configuration, which is\n * why one instance is shared. Two engines with private copies would fetch the\n * same endpoint twice and could disagree about one pair at one moment. See\n * `engine/EngineContext.ts`.\n */\nexport class CurrencyExchangeService {\n /**\n * Live rate tables cached from successful getRate() fetches, keyed by\n * uppercase base currency. Each table holds every rate the API returned\n * for that base (plus the base itself at 1), so any pair whose two codes\n * appear in one fresh table can be served synchronously, including\n * cross pairs via triangulation (EUR→GBP through a USD-base table).\n * Stale tables are ignored, not evicted; the next successful fetch for\n * the same base overwrites them.\n */\n private baseTables: Map<string, { fetchedAt: number; rates: Record<string, number> }> = new Map();\n\n /**\n * How long a fetched rate may be served synchronously by getRateSync().\n * Beyond this window callers fall through to the async path (expression\n * shows Pending until the fetch lands).\n */\n private static readonly RATE_FRESHNESS_MS = 15 * 60 * 1000;\n\n /**\n * Ticker → CoinGecko coin id, for the cryptocurrencies `isCurrency()`\n * recognizes. Frankfurter (the fiat rate source below) is ECB reference\n * rates only and has no concept of BTC/ETH/etc, routing a crypto code\n * through it as `base=BTC` fails outright, which is why crypto pairs\n * previously never resolved (see CurrencyAsyncResolver/VM.ts's ADD\n * handling for the two bugs that let that failure pass silently instead\n * of surfacing as a real fetch).\n */\n private static readonly CRYPTO_IDS: Record<string, string> = {\n BTC: \"bitcoin\", ETH: \"ethereum\", SOL: \"solana\", XRP: \"ripple\",\n ADA: \"cardano\", DOGE: \"dogecoin\", DOT: \"polkadot\",\n };\n\n private isCryptoCode(code: string): boolean {\n return code.toUpperCase() in CurrencyExchangeService.CRYPTO_IDS;\n }\n\n constructor() {}\n\n // ------------------------------------------------------------------------\n // RATE FETCHING\n // ------------------------------------------------------------------------\n\n /**\n * Timeout (ms) for currency exchange rate fetches.\n *\n * If the frankfurter API doesn't respond within this window, the fetch\n * is aborted, preventing indefinite \"Pending\" states in the playground\n * and Obsidian plugin when the exchange rate API is unreachable.\n */\n private static readonly FETCH_TIMEOUT_MS = 10_000;\n\n /**\n * Fetch the live exchange rate for converting 1 unit of `from` into `to`.\n *\n * Routes cryptocurrency codes (see {@link CRYPTO_IDS}) to CoinGecko and\n * everything else to Frankfurter (ECB reference rates, fiat-only). On\n * success, caches the whole returned rate table for `from` so subsequent\n * lookups, including cross-pairs via triangulation, can be served\n * synchronously by {@link getRateSync} within the freshness window.\n *\n * @throws If the currency code is unrecognized or the fetch fails/times out.\n */\n async getRate(from: string, to: string, signal?: AbortSignal): Promise<number> {\n const fromUpper = from.toUpperCase();\n const toUpper = to.toUpperCase();\n\n if (this.isCryptoCode(fromUpper) || this.isCryptoCode(toUpper)) {\n return this.getCryptoRate(fromUpper, toUpper, signal);\n }\n\n // Combine the caller's optional abort signal with a hard timeout so a\n // hanging currency API never blocks re-evaluation indefinitely.\n const { signal: fetchSignal, cleanup } = createTimeoutSignal(\n signal,\n CurrencyExchangeService.FETCH_TIMEOUT_MS,\n \"Currency API fetch\",\n );\n\n try {\n const response = await fetch(`https://api.frankfurter.dev/v2/rates?base=${fromUpper}`, { signal: fetchSignal });\n if (!response.ok) throw ErrorFactory.external(CurrencyErrorCodes.API_ERROR, `Currency API returned ${response.status}`, { status: response.status });\n const data = await response.json();\n // The v2 endpoint returns a flat array of { date, base, quote, rate }\n // entries (one per target currency) rather than the classic v1 shape\n // { base, date, rates: { CODE: rate } }. Reading data.rates against\n // the real response is always undefined, so every conversion used to\n // throw \"Unknown currency\" no matter which currencies were requested.\n // Accept both shapes so a future API revision back to the object form\n // doesn't silently break this again.\n const rates: Record<string, number> = Array.isArray(data)\n ? Object.fromEntries(\n data\n .filter((entry: unknown): entry is { quote: string; rate: number } => {\n if (typeof entry !== \"object\" || entry === null) return false;\n const candidate = entry as { quote?: unknown; rate?: unknown };\n return typeof candidate.quote === \"string\" && typeof candidate.rate === \"number\";\n })\n .map((entry: { quote: string; rate: number }) => [entry.quote.toUpperCase(), entry.rate])\n )\n : (data.rates ?? {});\n if (rates[toUpper] === undefined) throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });\n\n // The API returns ALL rates for the base currency, cache the whole\n // table so subsequent conversions (including cross pairs via\n // triangulation) resolve synchronously within the freshness window\n // instead of going Pending again.\n this.baseTables.set(fromUpper, {\n fetchedAt: Date.now(),\n rates: { ...rates, [fromUpper]: 1 },\n });\n\n return rates[toUpper];\n } finally {\n cleanup();\n }\n }\n\n /**\n * Crypto rate fetch, routed through CoinGecko's no-auth simple-price\n * endpoint instead of Frankfurter (fiat-only, has no BTC/ETH concept).\n * Handles all three combinations, crypto→crypto, crypto→fiat\n * fiat→crypto, via prices denominated in USD (or the target fiat\n * directly, which CoinGecko's `vs_currencies` also accepts), then\n * caches the result as a same-shaped base table so getRateSync /\n * convertSync keep working unchanged for crypto pairs too.\n */\n private async getCryptoRate(fromUpper: string, toUpper: string, signal?: AbortSignal): Promise<number> {\n const { signal: fetchSignal, cleanup } = createTimeoutSignal(\n signal,\n CurrencyExchangeService.FETCH_TIMEOUT_MS,\n \"Crypto price API fetch\",\n );\n\n try {\n const fromIsCrypto = this.isCryptoCode(fromUpper);\n const toIsCrypto = this.isCryptoCode(toUpper);\n let rate: number;\n\n if (fromIsCrypto && toIsCrypto) {\n const fromId = CurrencyExchangeService.CRYPTO_IDS[fromUpper];\n const toId = CurrencyExchangeService.CRYPTO_IDS[toUpper];\n const data = await this.fetchCoinGeckoPrices([fromId, toId], \"usd\", fetchSignal);\n const fromUsd = data[fromId]?.usd;\n const toUsd = data[toId]?.usd;\n if (typeof fromUsd !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${fromUpper}`, { code: fromUpper });\n if (typeof toUsd !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });\n rate = fromUsd / toUsd;\n } else if (fromIsCrypto) {\n const fromId = CurrencyExchangeService.CRYPTO_IDS[fromUpper];\n const vs = toUpper.toLowerCase();\n const data = await this.fetchCoinGeckoPrices([fromId], vs, fetchSignal);\n const value = data[fromId]?.[vs];\n if (typeof value !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });\n rate = value;\n } else {\n const toId = CurrencyExchangeService.CRYPTO_IDS[toUpper];\n const vs = fromUpper.toLowerCase();\n const data = await this.fetchCoinGeckoPrices([toId], vs, fetchSignal);\n const priceOfToInFrom = data[toId]?.[vs];\n if (typeof priceOfToInFrom !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${fromUpper}`, { code: fromUpper });\n rate = 1 / priceOfToInFrom;\n }\n\n // Cache as a single-pair base table, same shape Frankfurter fetches\n // produce, so getRateSync/convertSync's triangulation logic doesn't\n // need to know or care which source a rate came from.\n this.baseTables.set(fromUpper, {\n fetchedAt: Date.now(),\n rates: { [fromUpper]: 1, [toUpper]: rate },\n });\n\n return rate;\n } finally {\n cleanup();\n }\n }\n\n private async fetchCoinGeckoPrices(ids: string[], vsCurrency: string, signal: AbortSignal): Promise<Record<string, Record<string, number>>> {\n const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids.join(\",\")}&vs_currencies=${vsCurrency}`;\n const response = await fetch(url, { signal });\n if (!response.ok) throw ErrorFactory.external(CurrencyErrorCodes.CRYPTO_API_ERROR, `Crypto price API returned ${response.status}`, { status: response.status });\n return response.json();\n }\n\n /**\n * Seed a base rate table without a network fetch.\n *\n * Intended for tests and for future user-provided offline rates\n * production live data always comes from {@link getRate}. Seeded rates\n * obey the same freshness window as fetched ones.\n *\n * @param base - Base currency code (e.g. \"USD\").\n * @param rates - Map of currency code → rate relative to the base.\n */\n primeRates(base: string, rates: Record<string, number>): void {\n const baseUpper = base.toUpperCase();\n this.baseTables.set(baseUpper, {\n fetchedAt: Date.now(),\n rates: { ...rates, [baseUpper]: 1 },\n });\n }\n\n /**\n * Drop every cached/primed rate table.\n *\n * Mainly for test isolation: {@link sharedCurrencyExchange} is a\n * module-level singleton, so a rate primed or fetched by one test can\n * silently leak into a later test in the same file. Also usable in\n * production if a caller ever wants to force a full re-fetch.\n */\n clearRates(): void {\n this.baseTables.clear();\n }\n\n /**\n * Synchronous rate lookup: `1` for same-currency pairs, a cached LIVE\n * rate if one was fetched within {@link RATE_FRESHNESS_MS}, otherwise\n * `null`, callers fall through to the async fetch path and the\n * expression shows Pending until real data arrives.\n *\n * There is deliberately no hardcoded fallback table: a stale made-up\n * rate presented as a real conversion is worse than a Pending state.\n */\n getRateSync(from: string, to: string): number | null {\n const fromUpper = from.toUpperCase();\n const toUpper = to.toUpperCase();\n if (fromUpper === toUpper) {\n return 1;\n }\n const now = Date.now();\n for (const table of this.baseTables.values()) {\n if (now - table.fetchedAt > CurrencyExchangeService.RATE_FRESHNESS_MS) continue;\n const fromRate = table.rates[fromUpper];\n const toRate = table.rates[toUpper];\n if (fromRate && toRate) {\n return toRate / fromRate;\n }\n }\n return null;\n }\n\n /** Convert `value` from `from` to `to` using a freshly-fetched live rate (see {@link getRate}). */\n async convert(value: number, from: string, to: string): Promise<number> {\n const rate = await this.getRate(from, to);\n return value * rate;\n }\n\n /**\n * Get all currently cached fresh rates, keyed \"FROM:TO\".\n * @returns Snapshot of fresh live rates, or null when none are cached.\n */\n getAllRates(): Record<string, number> | null {\n const now = Date.now();\n const snapshot: Record<string, number> = {};\n let any = false;\n for (const [base, table] of this.baseTables) {\n if (now - table.fetchedAt > CurrencyExchangeService.RATE_FRESHNESS_MS) continue;\n for (const [code, rate] of Object.entries(table.rates)) {\n snapshot[`${base}:${code}`] = rate;\n any = true;\n }\n }\n return any ? snapshot : null;\n }\n\n /**\n * Check whether any fresh live rates are currently cached.\n */\n hasRates(): boolean {\n return this.getAllRates() !== null;\n }\n\n /**\n * Synchronous conversion using cached rates only\n * Returns null if rate not in cache\n */\n convertSync(value: number, from: string, to: string): number | null {\n const rate = this.getRateSync(from, to);\n if (rate === null) return null;\n return value * rate;\n }\n\n // ------------------------------------------------------------------------\n // CURRENCY VALIDATION\n // ------------------------------------------------------------------------\n\n /**\n * Check whether `code` is a recognized currency code: any active ISO 4217\n * code, or one of the cryptocurrencies in {@link CRYPTO_IDS}.\n *\n * This used to be a hand-written list of forty-six codes, which meant\n * `$100 in UAH` returned an unconverted hundred dollars rather than saying\n * it could not convert. Answering from the standard rather than from\n * whichever codes happened to get added is what stops that class of bug.\n *\n * Recognising a code is not the same as having a rate for it. That is\n * answered later, by the exchange provider; conflating the two is what\n * produced the silent failure.\n */\n isCurrency(code: string): boolean {\n return isIso4217(code) || this.isCryptoCode(code);\n }\n}\n\n// ============================================================================\n// SINGLETON INSTANCE\n// ============================================================================\n\n/** The shared rate cache. See {@link CurrencyExchangeService} for why it is shared. */\nexport const currencyExchangeService = new CurrencyExchangeService();\n\n// Export for backward compatibility\n/** Alias for {@link currencyExchangeService}, kept for older imports. */\nexport const sharedCurrencyExchange = currencyExchangeService;\n\n// Default export\nexport default currencyExchangeService;\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/uom/Iso4217.ts","../src/uom/CurrencyExchange.ts"],"names":["createTimeoutSignal","ErrorFactory"],"mappings":"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","file":"chunk-A2N2GFCG.cjs","sourcesContent":["/**\n * Every active ISO 4217 currency code.\n *\n * This exists because `CurrencyExchange.isCurrency()` used to hold a\n * hand-written list of forty-six codes, and a code missing from it did not\n * error: `$100 in UAH` returned the original hundred dollars, unconverted, as\n * though the rate were 1. A wrong answer, not a missing feature.\n *\n * The list is the ISO 4217 active set, so \"is this a currency\" is answered by\n * the standard rather than by whichever codes happened to get added. Whether a\n * *rate* is available for one is a separate question, answered at resolution\n * time by whatever the exchange provider supports; conflating the two is what\n * produced the silent failure.\n *\n * Deliberately excluded:\n * - The historical/withdrawn codes (DEM, FRF, and the rest). They are not\n * active, no provider quotes them, and recognising them would only move the\n * failure later.\n * - The X-series non-currencies: XAU/XAG/XPT/XPD (metals), XDR (IMF special\n * drawing rights), XTS (reserved for testing) and XXX (explicitly \"no\n * currency\"). XXX in particular must never resolve.\n *\n * Cryptocurrencies are not here either. They are not ISO 4217 and are tracked\n * separately in `CurrencyExchangeService.CRYPTO_IDS`.\n */\nexport const ISO_4217_CODES: ReadonlySet<string> = new Set([\n\t\"AED\", \"AFN\", \"ALL\", \"AMD\", \"ANG\", \"AOA\", \"ARS\", \"AUD\", \"AWG\", \"AZN\",\n\t\"BAM\", \"BBD\", \"BDT\", \"BGN\", \"BHD\", \"BIF\", \"BMD\", \"BND\", \"BOB\", \"BOV\",\n\t\"BRL\", \"BSD\", \"BTN\", \"BWP\", \"BYN\", \"BZD\",\n\t\"CAD\", \"CDF\", \"CHE\", \"CHF\", \"CHW\", \"CLF\", \"CLP\", \"CNY\", \"COP\", \"COU\",\n\t\"CRC\", \"CUP\", \"CVE\", \"CZK\",\n\t\"DJF\", \"DKK\", \"DOP\", \"DZD\",\n\t\"EGP\", \"ERN\", \"ETB\", \"EUR\",\n\t\"FJD\", \"FKP\",\n\t\"GBP\", \"GEL\", \"GHS\", \"GIP\", \"GMD\", \"GNF\", \"GTQ\", \"GYD\",\n\t\"HKD\", \"HNL\", \"HTG\", \"HUF\",\n\t\"IDR\", \"ILS\", \"INR\", \"IQD\", \"IRR\", \"ISK\",\n\t\"JMD\", \"JOD\", \"JPY\",\n\t\"KES\", \"KGS\", \"KHR\", \"KMF\", \"KPW\", \"KRW\", \"KWD\", \"KYD\", \"KZT\",\n\t\"LAK\", \"LBP\", \"LKR\", \"LRD\", \"LSL\", \"LYD\",\n\t\"MAD\", \"MDL\", \"MGA\", \"MKD\", \"MMK\", \"MNT\", \"MOP\", \"MRU\", \"MUR\", \"MVR\",\n\t\"MWK\", \"MXN\", \"MXV\", \"MYR\", \"MZN\",\n\t\"NAD\", \"NGN\", \"NIO\", \"NOK\", \"NPR\", \"NZD\",\n\t\"OMR\",\n\t\"PAB\", \"PEN\", \"PGK\", \"PHP\", \"PKR\", \"PLN\", \"PYG\",\n\t\"QAR\",\n\t\"RON\", \"RSD\", \"RUB\", \"RWF\",\n\t\"SAR\", \"SBD\", \"SCR\", \"SDG\", \"SEK\", \"SGD\", \"SHP\", \"SLE\", \"SOS\", \"SRD\",\n\t\"SSP\", \"STN\", \"SVC\", \"SYP\", \"SZL\",\n\t\"THB\", \"TJS\", \"TMT\", \"TND\", \"TOP\", \"TRY\", \"TTD\", \"TWD\", \"TZS\",\n\t\"UAH\", \"UGX\", \"USD\", \"USN\", \"UYI\", \"UYU\", \"UYW\", \"UZS\",\n\t\"VED\", \"VES\", \"VND\", \"VUV\",\n\t\"WST\",\n\t\"XAF\", \"XCD\", \"XCG\", \"XOF\", \"XPF\",\n\t\"YER\",\n\t\"ZAR\", \"ZMW\", \"ZWG\",\n]);\n\n/**\n * Whether `code` is an active ISO 4217 currency code.\n *\n * @param code - A three-letter code, in any case.\n */\nexport function isIso4217(code: string): boolean {\n\treturn ISO_4217_CODES.has(code.toUpperCase());\n}\n","/**\n * Production-grade currency exchange service with scalable architecture\n * Integrates with DataQueryService for worker-based execution\n */\n\nimport { isIso4217 } from \"@solve-js/uom/Iso4217\";\nimport { createTimeoutSignal } from \"@solve-js/utilities/TimeoutSignal\";\nimport { ErrorFactory } from \"@solve-js/errors/UnifiedErrorFramework\";\n\n/**\n * Error codes for this service. Co-located rather than unioned into\n * `errors/ErrorCode.ts`'s core catalog (that catalog is scoped to the\n * parser/VM/engine/errors/config/lexer layers, not yet the ~17 domain\n * packages. See that file's module doc for the intended per-package\n * pattern this follows).\n */\nexport const CurrencyErrorCodes = {\n /** Frankfurter's rates endpoint returned a non-OK HTTP status. */\n API_ERROR: \"CURRENCY_API_ERROR\",\n /** A requested currency/crypto code isn't in the fetched rate table, an unrecognized code, not an API failure. */\n UNKNOWN_CODE: \"UNKNOWN_CURRENCY_CODE\",\n /** CoinGecko's simple-price endpoint returned a non-OK HTTP status. */\n CRYPTO_API_ERROR: \"CRYPTO_PRICE_API_ERROR\",\n} as const;\n\n// ============================================================================\n// CURRENCY EXCHANGE SERVICE\n// ============================================================================\n\n/**\n * Caches exchange rates fetched from an external source.\n *\n * Rates are global market data rather than per-engine configuration, which is\n * why one instance is shared. Two engines with private copies would fetch the\n * same endpoint twice and could disagree about one pair at one moment. See\n * `engine/EngineContext.ts`.\n */\nexport class CurrencyExchangeService {\n /**\n * Live rate tables cached from successful getRate() fetches, keyed by\n * uppercase base currency. Each table holds every rate the API returned\n * for that base (plus the base itself at 1), so any pair whose two codes\n * appear in one fresh table can be served synchronously, including\n * cross pairs via triangulation (EUR→GBP through a USD-base table).\n * Stale tables are ignored, not evicted; the next successful fetch for\n * the same base overwrites them.\n */\n private baseTables: Map<string, { fetchedAt: number; rates: Record<string, number> }> = new Map();\n\n /**\n * How long a fetched rate may be served synchronously by getRateSync().\n * Beyond this window callers fall through to the async path (expression\n * shows Pending until the fetch lands).\n */\n private static readonly RATE_FRESHNESS_MS = 15 * 60 * 1000;\n\n /**\n * Ticker → CoinGecko coin id, for the cryptocurrencies `isCurrency()`\n * recognizes. Frankfurter (the fiat rate source below) is ECB reference\n * rates only and has no concept of BTC/ETH/etc, routing a crypto code\n * through it as `base=BTC` fails outright, which is why crypto pairs\n * previously never resolved (see CurrencyAsyncResolver/VM.ts's ADD\n * handling for the two bugs that let that failure pass silently instead\n * of surfacing as a real fetch).\n */\n private static readonly CRYPTO_IDS: Record<string, string> = {\n BTC: \"bitcoin\", ETH: \"ethereum\", SOL: \"solana\", XRP: \"ripple\",\n ADA: \"cardano\", DOGE: \"dogecoin\", DOT: \"polkadot\",\n };\n\n private isCryptoCode(code: string): boolean {\n return code.toUpperCase() in CurrencyExchangeService.CRYPTO_IDS;\n }\n\n constructor() {}\n\n // ------------------------------------------------------------------------\n // RATE FETCHING\n // ------------------------------------------------------------------------\n\n /**\n * Timeout (ms) for currency exchange rate fetches.\n *\n * If the frankfurter API doesn't respond within this window, the fetch\n * is aborted, preventing indefinite \"Pending\" states in the playground\n * and Obsidian plugin when the exchange rate API is unreachable.\n */\n private static readonly FETCH_TIMEOUT_MS = 10_000;\n\n /**\n * Fetch the live exchange rate for converting 1 unit of `from` into `to`.\n *\n * Routes cryptocurrency codes (see {@link CRYPTO_IDS}) to CoinGecko and\n * everything else to Frankfurter (ECB reference rates, fiat-only). On\n * success, caches the whole returned rate table for `from` so subsequent\n * lookups, including cross-pairs via triangulation, can be served\n * synchronously by {@link getRateSync} within the freshness window.\n *\n * @throws If the currency code is unrecognized or the fetch fails/times out.\n */\n async getRate(from: string, to: string, signal?: AbortSignal): Promise<number> {\n const fromUpper = from.toUpperCase();\n const toUpper = to.toUpperCase();\n\n if (this.isCryptoCode(fromUpper) || this.isCryptoCode(toUpper)) {\n return this.getCryptoRate(fromUpper, toUpper, signal);\n }\n\n // Combine the caller's optional abort signal with a hard timeout so a\n // hanging currency API never blocks re-evaluation indefinitely.\n const { signal: fetchSignal, cleanup } = createTimeoutSignal(\n signal,\n CurrencyExchangeService.FETCH_TIMEOUT_MS,\n \"Currency API fetch\",\n );\n\n try {\n const response = await fetch(`https://api.frankfurter.dev/v2/rates?base=${fromUpper}`, { signal: fetchSignal });\n if (!response.ok) throw ErrorFactory.external(CurrencyErrorCodes.API_ERROR, `Currency API returned ${response.status}`, { status: response.status });\n const data = await response.json();\n // The v2 endpoint returns a flat array of { date, base, quote, rate }\n // entries (one per target currency) rather than the classic v1 shape\n // { base, date, rates: { CODE: rate } }. Reading data.rates against\n // the real response is always undefined, so every conversion used to\n // throw \"Unknown currency\" no matter which currencies were requested.\n // Accept both shapes so a future API revision back to the object form\n // doesn't silently break this again.\n const rates: Record<string, number> = Array.isArray(data)\n ? Object.fromEntries(\n data\n .filter((entry: unknown): entry is { quote: string; rate: number } => {\n if (typeof entry !== \"object\" || entry === null) return false;\n const candidate = entry as { quote?: unknown; rate?: unknown };\n return typeof candidate.quote === \"string\" && typeof candidate.rate === \"number\";\n })\n .map((entry: { quote: string; rate: number }) => [entry.quote.toUpperCase(), entry.rate])\n )\n : (data.rates ?? {});\n if (rates[toUpper] === undefined) throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });\n\n // The API returns ALL rates for the base currency, cache the whole\n // table so subsequent conversions (including cross pairs via\n // triangulation) resolve synchronously within the freshness window\n // instead of going Pending again.\n this.baseTables.set(fromUpper, {\n fetchedAt: Date.now(),\n rates: { ...rates, [fromUpper]: 1 },\n });\n\n return rates[toUpper];\n } finally {\n cleanup();\n }\n }\n\n /**\n * Crypto rate fetch, routed through CoinGecko's no-auth simple-price\n * endpoint instead of Frankfurter (fiat-only, has no BTC/ETH concept).\n * Handles all three combinations, crypto→crypto, crypto→fiat\n * fiat→crypto, via prices denominated in USD (or the target fiat\n * directly, which CoinGecko's `vs_currencies` also accepts), then\n * caches the result as a same-shaped base table so getRateSync /\n * convertSync keep working unchanged for crypto pairs too.\n */\n private async getCryptoRate(fromUpper: string, toUpper: string, signal?: AbortSignal): Promise<number> {\n const { signal: fetchSignal, cleanup } = createTimeoutSignal(\n signal,\n CurrencyExchangeService.FETCH_TIMEOUT_MS,\n \"Crypto price API fetch\",\n );\n\n try {\n const fromIsCrypto = this.isCryptoCode(fromUpper);\n const toIsCrypto = this.isCryptoCode(toUpper);\n let rate: number;\n\n if (fromIsCrypto && toIsCrypto) {\n const fromId = CurrencyExchangeService.CRYPTO_IDS[fromUpper];\n const toId = CurrencyExchangeService.CRYPTO_IDS[toUpper];\n const data = await this.fetchCoinGeckoPrices([fromId, toId], \"usd\", fetchSignal);\n const fromUsd = data[fromId]?.usd;\n const toUsd = data[toId]?.usd;\n if (typeof fromUsd !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${fromUpper}`, { code: fromUpper });\n if (typeof toUsd !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });\n rate = fromUsd / toUsd;\n } else if (fromIsCrypto) {\n const fromId = CurrencyExchangeService.CRYPTO_IDS[fromUpper];\n const vs = toUpper.toLowerCase();\n const data = await this.fetchCoinGeckoPrices([fromId], vs, fetchSignal);\n const value = data[fromId]?.[vs];\n if (typeof value !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${toUpper}`, { code: toUpper });\n rate = value;\n } else {\n const toId = CurrencyExchangeService.CRYPTO_IDS[toUpper];\n const vs = fromUpper.toLowerCase();\n const data = await this.fetchCoinGeckoPrices([toId], vs, fetchSignal);\n const priceOfToInFrom = data[toId]?.[vs];\n if (typeof priceOfToInFrom !== \"number\") throw ErrorFactory.validation(CurrencyErrorCodes.UNKNOWN_CODE, `Unknown currency: ${fromUpper}`, { code: fromUpper });\n rate = 1 / priceOfToInFrom;\n }\n\n // Cache as a single-pair base table, same shape Frankfurter fetches\n // produce, so getRateSync/convertSync's triangulation logic doesn't\n // need to know or care which source a rate came from.\n this.baseTables.set(fromUpper, {\n fetchedAt: Date.now(),\n rates: { [fromUpper]: 1, [toUpper]: rate },\n });\n\n return rate;\n } finally {\n cleanup();\n }\n }\n\n private async fetchCoinGeckoPrices(ids: string[], vsCurrency: string, signal: AbortSignal): Promise<Record<string, Record<string, number>>> {\n const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids.join(\",\")}&vs_currencies=${vsCurrency}`;\n const response = await fetch(url, { signal });\n if (!response.ok) throw ErrorFactory.external(CurrencyErrorCodes.CRYPTO_API_ERROR, `Crypto price API returned ${response.status}`, { status: response.status });\n return response.json();\n }\n\n /**\n * Seed a base rate table without a network fetch.\n *\n * Intended for tests and for future user-provided offline rates\n * production live data always comes from {@link getRate}. Seeded rates\n * obey the same freshness window as fetched ones.\n *\n * @param base - Base currency code (e.g. \"USD\").\n * @param rates - Map of currency code → rate relative to the base.\n */\n primeRates(base: string, rates: Record<string, number>): void {\n const baseUpper = base.toUpperCase();\n this.baseTables.set(baseUpper, {\n fetchedAt: Date.now(),\n rates: { ...rates, [baseUpper]: 1 },\n });\n }\n\n /**\n * Drop every cached/primed rate table.\n *\n * Mainly for test isolation: {@link sharedCurrencyExchange} is a\n * module-level singleton, so a rate primed or fetched by one test can\n * silently leak into a later test in the same file. Also usable in\n * production if a caller ever wants to force a full re-fetch.\n */\n clearRates(): void {\n this.baseTables.clear();\n }\n\n /**\n * Synchronous rate lookup: `1` for same-currency pairs, a cached LIVE\n * rate if one was fetched within {@link RATE_FRESHNESS_MS}, otherwise\n * `null`, callers fall through to the async fetch path and the\n * expression shows Pending until real data arrives.\n *\n * There is deliberately no hardcoded fallback table: a stale made-up\n * rate presented as a real conversion is worse than a Pending state.\n */\n getRateSync(from: string, to: string): number | null {\n const fromUpper = from.toUpperCase();\n const toUpper = to.toUpperCase();\n if (fromUpper === toUpper) {\n return 1;\n }\n const now = Date.now();\n for (const table of this.baseTables.values()) {\n if (now - table.fetchedAt > CurrencyExchangeService.RATE_FRESHNESS_MS) continue;\n const fromRate = table.rates[fromUpper];\n const toRate = table.rates[toUpper];\n if (fromRate && toRate) {\n return toRate / fromRate;\n }\n }\n return null;\n }\n\n /** Convert `value` from `from` to `to` using a freshly-fetched live rate (see {@link getRate}). */\n async convert(value: number, from: string, to: string): Promise<number> {\n const rate = await this.getRate(from, to);\n return value * rate;\n }\n\n /**\n * Get all currently cached fresh rates, keyed \"FROM:TO\".\n * @returns Snapshot of fresh live rates, or null when none are cached.\n */\n getAllRates(): Record<string, number> | null {\n const now = Date.now();\n const snapshot: Record<string, number> = {};\n let any = false;\n for (const [base, table] of this.baseTables) {\n if (now - table.fetchedAt > CurrencyExchangeService.RATE_FRESHNESS_MS) continue;\n for (const [code, rate] of Object.entries(table.rates)) {\n snapshot[`${base}:${code}`] = rate;\n any = true;\n }\n }\n return any ? snapshot : null;\n }\n\n /**\n * Check whether any fresh live rates are currently cached.\n */\n hasRates(): boolean {\n return this.getAllRates() !== null;\n }\n\n /**\n * Synchronous conversion using cached rates only\n * Returns null if rate not in cache\n */\n convertSync(value: number, from: string, to: string): number | null {\n const rate = this.getRateSync(from, to);\n if (rate === null) return null;\n return value * rate;\n }\n\n // ------------------------------------------------------------------------\n // CURRENCY VALIDATION\n // ------------------------------------------------------------------------\n\n /**\n * Check whether `code` is a recognized currency code: any active ISO 4217\n * code, or one of the cryptocurrencies in {@link CRYPTO_IDS}.\n *\n * This used to be a hand-written list of forty-six codes, which meant\n * `$100 in UAH` returned an unconverted hundred dollars rather than saying\n * it could not convert. Answering from the standard rather than from\n * whichever codes happened to get added is what stops that class of bug.\n *\n * Recognising a code is not the same as having a rate for it. That is\n * answered later, by the exchange provider; conflating the two is what\n * produced the silent failure.\n */\n isCurrency(code: string): boolean {\n return isIso4217(code) || this.isCryptoCode(code);\n }\n}\n\n// ============================================================================\n// SINGLETON INSTANCE\n// ============================================================================\n\n/** The shared rate cache. See {@link CurrencyExchangeService} for why it is shared. */\nexport const currencyExchangeService = new CurrencyExchangeService();\n\n// Export for backward compatibility\n/** Alias for {@link currencyExchangeService}, kept for older imports. */\nexport const sharedCurrencyExchange = currencyExchangeService;\n\n// Default export\nexport default currencyExchangeService;\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utilities/Strings.ts
|
|
4
|
+
function stripQuotes(value) {
|
|
5
|
+
return value.startsWith('"') && value.endsWith('"') && value.length >= 2 ? value.slice(1, -1) : value;
|
|
6
|
+
}
|
|
7
|
+
function countLines(text, stopAfter = Number.MAX_SAFE_INTEGER) {
|
|
8
|
+
let count = 1;
|
|
9
|
+
let index = text.indexOf("\n");
|
|
10
|
+
while (index !== -1) {
|
|
11
|
+
if (++count > stopAfter) return count;
|
|
12
|
+
index = text.indexOf("\n", index + 1);
|
|
13
|
+
}
|
|
14
|
+
return count;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.countLines = countLines;
|
|
18
|
+
exports.stripQuotes = stripQuotes;
|
|
19
|
+
//# sourceMappingURL=chunk-ADBA3XBE.cjs.map
|
|
20
|
+
//# sourceMappingURL=chunk-ADBA3XBE.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utilities/Strings.ts"],"names":[],"mappings":";;;AASO,SAAS,YAAY,KAAA,EAAuB;AAClD,EAAA,OAAO,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,IAAK,MAAM,QAAA,CAAS,GAAG,CAAA,IAAK,KAAA,CAAM,UAAU,CAAA,GACpE,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,GACjB,KAAA;AACJ;AAeO,SAAS,UAAA,CAAW,IAAA,EAAc,SAAA,GAAoB,MAAA,CAAO,gBAAA,EAA0B;AAC7F,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAC7B,EAAA,OAAO,UAAU,EAAA,EAAI;AACpB,IAAA,IAAI,EAAE,KAAA,GAAQ,SAAA,EAAW,OAAO,KAAA;AAChC,IAAA,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,KAAA,GAAQ,CAAC,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,KAAA;AACR","file":"chunk-ADBA3XBE.cjs","sourcesContent":["/**\n * Strips a single layer of matching double quotes from a string, if present.\n * Returns the input unchanged if it isn't fully wrapped in quotes.\n *\n * Was needed for STRING token values, which the lexer used to emit with their\n * surrounding `\"...\"` quotes still attached. `tokenizeString()` now strips them\n * when it builds the token, so on that path this is a no-op kept as a defence\n * against text arriving from somewhere else already quoted.\n */\nexport function stripQuotes(value: string): string {\n\treturn value.startsWith('\"') && value.endsWith('\"') && value.length >= 2\n\t\t? value.slice(1, -1)\n\t\t: value;\n}\n\n/**\n * How many lines `text` has, without splitting it into an array.\n *\n * `split(\"\\n\").length` answers the same question and allocates the whole\n * document to do it, which is the wrong shape for a caller whose next move may\n * be to refuse the document for being too large. Counting stops as soon as the\n * answer is \"more than the caller cares about\".\n *\n * @param text - The document.\n * @param stopAfter - Stop counting once past this many lines.\n * @returns The line count, or `stopAfter + 1` for anything longer. A document\n * with no newline in it is one line, matching `split`.\n */\nexport function countLines(text: string, stopAfter: number = Number.MAX_SAFE_INTEGER): number {\n\tlet count = 1;\n\tlet index = text.indexOf(\"\\n\");\n\twhile (index !== -1) {\n\t\tif (++count > stopAfter) return count;\n\t\tindex = text.indexOf(\"\\n\", index + 1);\n\t}\n\treturn count;\n}\n"]}
|
|
@@ -82,12 +82,18 @@ var UNIT_TABLE = {
|
|
|
82
82
|
"square decametres": [1, 100],
|
|
83
83
|
"dam\xB2": [1, 100],
|
|
84
84
|
"dam2": [1, 100],
|
|
85
|
-
"square decimeter": [1, 0.
|
|
86
|
-
|
|
87
|
-
"square
|
|
88
|
-
|
|
89
|
-
"
|
|
90
|
-
|
|
85
|
+
"square decimeter": [1, 0.01],
|
|
86
|
+
// corrected, upstream says 0.1
|
|
87
|
+
"square decimetre": [1, 0.01],
|
|
88
|
+
// corrected, upstream says 0.1
|
|
89
|
+
"square decimeters": [1, 0.01],
|
|
90
|
+
// corrected, upstream says 0.1
|
|
91
|
+
"square decimetres": [1, 0.01],
|
|
92
|
+
// corrected, upstream says 0.1
|
|
93
|
+
"dm\xB2": [1, 0.01],
|
|
94
|
+
// corrected, upstream says 0.1
|
|
95
|
+
"dm2": [1, 0.01],
|
|
96
|
+
// corrected, upstream says 0.1
|
|
91
97
|
"square centimeter": [1, 1e-4],
|
|
92
98
|
"square centimetre": [1, 1e-4],
|
|
93
99
|
"square centimeters": [1, 1e-4],
|
|
@@ -1496,8 +1502,8 @@ var BEST_UNITS_METRIC = {
|
|
|
1496
1502
|
// force
|
|
1497
1503
|
5: [["Hz", 1], ["kHz", 1e3], ["MHz", 1e6], ["GHz", 1e9], ["THz", 1e12], ["PHz", 1e15]],
|
|
1498
1504
|
// frequency
|
|
1499
|
-
6: [["lux", 1], ["
|
|
1500
|
-
// illuminance
|
|
1505
|
+
6: [["lux", 1], ["klx", 1e3]],
|
|
1506
|
+
// illuminance, corrected
|
|
1501
1507
|
7: [["mm", 1], ["cm", 10], ["m", 1e3], ["km", 1e6]],
|
|
1502
1508
|
// length
|
|
1503
1509
|
8: [["cd/m\xB2", 1]],
|
|
@@ -1682,5 +1688,5 @@ exports.MEASURE_KIND_NAMES = MEASURE_KIND_NAMES;
|
|
|
1682
1688
|
exports.MEASURE_SYMBOLS = MEASURE_SYMBOLS;
|
|
1683
1689
|
exports.UNIT_DIFFERENCES = UNIT_DIFFERENCES;
|
|
1684
1690
|
exports.UNIT_TABLE = UNIT_TABLE;
|
|
1685
|
-
//# sourceMappingURL=chunk-
|
|
1686
|
-
//# sourceMappingURL=chunk-
|
|
1691
|
+
//# sourceMappingURL=chunk-AMOJYLG2.cjs.map
|
|
1692
|
+
//# sourceMappingURL=chunk-AMOJYLG2.cjs.map
|