@vielzeug/coins 1.0.4 → 2.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/README.md +50 -222
- package/dist/aggregate.cjs +2 -0
- package/dist/aggregate.cjs.map +1 -0
- package/dist/aggregate.d.ts +11 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +44 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/coins.cjs +1 -1
- package/dist/coins.cjs.map +1 -1
- package/dist/coins.iife.js +1 -1
- package/dist/coins.iife.js.map +1 -1
- package/dist/coins.js +1 -1
- package/dist/coins.js.map +1 -1
- package/dist/currency.cjs +2 -0
- package/dist/currency.cjs.map +1 -0
- package/dist/currency.d.ts +38 -0
- package/dist/currency.d.ts.map +1 -0
- package/dist/currency.js +55 -0
- package/dist/currency.js.map +1 -0
- package/dist/decimal.cjs +2 -0
- package/dist/decimal.cjs.map +1 -0
- package/dist/decimal.d.ts +7 -0
- package/dist/decimal.d.ts.map +1 -0
- package/dist/decimal.js +52 -0
- package/dist/decimal.js.map +1 -0
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +6 -42
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -6
- package/dist/errors.js.map +1 -1
- package/dist/exchange.cjs +1 -1
- package/dist/exchange.cjs.map +1 -1
- package/dist/exchange.d.ts +9 -27
- package/dist/exchange.d.ts.map +1 -1
- package/dist/exchange.js +24 -12
- package/dist/exchange.js.map +1 -1
- package/dist/format.cjs +1 -1
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.ts +2 -39
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +57 -94
- package/dist/format.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/money.cjs +1 -1
- package/dist/money.cjs.map +1 -1
- package/dist/money.d.ts +26 -245
- package/dist/money.d.ts.map +1 -1
- package/dist/money.js +71 -184
- package/dist/money.js.map +1 -1
- package/dist/serialization.cjs +2 -0
- package/dist/serialization.cjs.map +1 -0
- package/dist/serialization.d.ts +6 -0
- package/dist/serialization.d.ts.map +1 -0
- package/dist/serialization.js +45 -0
- package/dist/serialization.js.map +1 -0
- package/dist/types.d.ts +35 -52
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/_cache.cjs +0 -2
- package/dist/_cache.cjs.map +0 -1
- package/dist/_cache.d.ts +0 -6
- package/dist/_cache.d.ts.map +0 -1
- package/dist/_cache.js +0 -14
- package/dist/_cache.js.map +0 -1
- package/dist/_dev.cjs +0 -2
- package/dist/_dev.cjs.map +0 -1
- package/dist/_dev.d.ts +0 -2
- package/dist/_dev.d.ts.map +0 -1
- package/dist/_dev.js +0 -9
- package/dist/_dev.js.map +0 -1
- package/dist/utils.cjs +0 -2
- package/dist/utils.cjs.map +0 -1
- package/dist/utils.d.ts +0 -66
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -66
- package/dist/utils.js.map +0 -1
package/dist/exchange.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./decimal.cjs"),n=require("./currency.cjs"),r=require("./money.cjs");function i({from:r,to:i,value:a}){if(!n.isCurrency(r)||!n.isCurrency(i))throw new e.CoinsError(`INVALID_CURRENCY`,`Exchange rate requires registered currencies`);let o=t.decimal(a);if(o.numerator<0n)throw new e.CoinsError(`INVALID_DECIMAL`,`Exchange rates cannot be negative`);return Object.freeze({from:r,to:i,value:o})}function a(n,i,a={}){if(!r.isMoney(n)||!o(i))throw new e.CoinsError(`INVALID_MONEY`,`Exchange requires canonical money and rate values`);if(n.currency!==i.from)throw new e.CurrencyMismatchError(n.currency.code,i.from.code);let s=n.amount*i.value.numerator*10n**BigInt(i.to.minorUnit),c=i.value.denominator*10n**BigInt(i.from.minorUnit);return r.withMinor(t.roundDivision(s,c,a.rounding??`halfAwayFromZero`),i.to)}function o(e){if(typeof e!=`object`||!e||!Object.isFrozen(e))return!1;let t=e;return n.isCurrency(t.from)&&n.isCurrency(t.to)&&typeof t.value==`object`&&t.value!==null&&Object.isFrozen(t.value)&&typeof t.value.numerator==`bigint`&&t.value.numerator>=0n&&typeof t.value.denominator==`bigint`&&t.value.denominator>0n}exports.exchange=a,exports.exchangeRate=i;
|
|
2
2
|
//# sourceMappingURL=exchange.cjs.map
|
package/dist/exchange.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.cjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["import type { ExchangeRate, Money, RoundingMode } from './types';\n\nimport {
|
|
1
|
+
{"version":3,"file":"exchange.cjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["import type { Currency, ExchangeRate, Money, RoundingMode } from './types';\n\nimport { isCurrency } from './currency';\nimport { decimal, roundDivision } from './decimal';\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport { isMoney, withMinor } from './money';\n\nexport function exchangeRate<From extends Currency, To extends Currency>({\n from,\n to,\n value,\n}: {\n from: From;\n to: To;\n value: string;\n}): ExchangeRate<From, To> {\n if (!isCurrency(from) || !isCurrency(to))\n throw new CoinsError('INVALID_CURRENCY', 'Exchange rate requires registered currencies');\n\n const parsed = decimal(value);\n\n if (parsed.numerator < 0n) throw new CoinsError('INVALID_DECIMAL', 'Exchange rates cannot be negative');\n\n return Object.freeze({ from, to, value: parsed });\n}\n\nexport function exchange<From extends Currency, To extends Currency>(\n value: Money<From>,\n rate: ExchangeRate<From, To>,\n options: { rounding?: RoundingMode } = {},\n): Money<To> {\n if (!isMoney(value) || !isValidRate(rate))\n throw new CoinsError('INVALID_MONEY', 'Exchange requires canonical money and rate values');\n\n if (value.currency !== rate.from) throw new CurrencyMismatchError(value.currency.code, rate.from.code);\n\n const numerator = value.amount * rate.value.numerator * 10n ** BigInt(rate.to.minorUnit);\n const denominator = rate.value.denominator * 10n ** BigInt(rate.from.minorUnit);\n\n return withMinor(roundDivision(numerator, denominator, options.rounding ?? 'halfAwayFromZero'), rate.to);\n}\n\nfunction isValidRate(value: unknown): value is ExchangeRate {\n if (typeof value !== 'object' || value === null || !Object.isFrozen(value)) return false;\n\n const rate = value as Partial<ExchangeRate>;\n\n return (\n isCurrency(rate.from) &&\n isCurrency(rate.to) &&\n typeof rate.value === 'object' &&\n rate.value !== null &&\n Object.isFrozen(rate.value) &&\n typeof rate.value.numerator === 'bigint' &&\n rate.value.numerator >= 0n &&\n typeof rate.value.denominator === 'bigint' &&\n rate.value.denominator > 0n\n );\n}\n"],"mappings":"gHAOA,SAAgB,EAAyD,CACvE,OACA,KACA,SAKyB,CACzB,GAAI,CAAC,EAAA,WAAW,CAAI,GAAK,CAAC,EAAA,WAAW,CAAE,EACrC,MAAM,IAAI,EAAA,WAAW,mBAAoB,8CAA8C,EAEzF,IAAM,EAAS,EAAA,QAAQ,CAAK,EAE5B,GAAI,EAAO,UAAY,GAAI,MAAM,IAAI,EAAA,WAAW,kBAAmB,mCAAmC,EAEtG,OAAO,OAAO,OAAO,CAAE,OAAM,KAAI,MAAO,CAAO,CAAC,CAClD,CAEA,SAAgB,EACd,EACA,EACA,EAAuC,CAAC,EAC7B,CACX,GAAI,CAAC,EAAA,QAAQ,CAAK,GAAK,CAAC,EAAY,CAAI,EACtC,MAAM,IAAI,EAAA,WAAW,gBAAiB,mDAAmD,EAE3F,GAAI,EAAM,WAAa,EAAK,KAAM,MAAM,IAAI,EAAA,sBAAsB,EAAM,SAAS,KAAM,EAAK,KAAK,IAAI,EAErG,IAAM,EAAY,EAAM,OAAS,EAAK,MAAM,UAAY,KAAO,OAAO,EAAK,GAAG,SAAS,EACjF,EAAc,EAAK,MAAM,YAAc,KAAO,OAAO,EAAK,KAAK,SAAS,EAE9E,OAAO,EAAA,UAAU,EAAA,cAAc,EAAW,EAAa,EAAQ,UAAY,kBAAkB,EAAG,EAAK,EAAE,CACzG,CAEA,SAAS,EAAY,EAAuC,CAC1D,GAAI,OAAO,GAAU,WAAY,GAAkB,CAAC,OAAO,SAAS,CAAK,EAAG,MAAO,GAEnF,IAAM,EAAO,EAEb,OACE,EAAA,WAAW,EAAK,IAAI,GACpB,EAAA,WAAW,EAAK,EAAE,GAClB,OAAO,EAAK,OAAU,UACtB,EAAK,QAAU,MACf,OAAO,SAAS,EAAK,KAAK,GAC1B,OAAO,EAAK,MAAM,WAAc,UAChC,EAAK,MAAM,WAAa,IACxB,OAAO,EAAK,MAAM,aAAgB,UAClC,EAAK,MAAM,YAAc,EAE7B"}
|
package/dist/exchange.d.ts
CHANGED
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
import type { ExchangeRate, Money, RoundingMode } from './types';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Defaults to `'half-away-from-zero'`.
|
|
11
|
-
*
|
|
12
|
-
* @throws {CurrencyMismatchError} If `money.currency` does not match `rate.from`.
|
|
13
|
-
* Note: `rate.from` is validated implicitly — because `money.currency` is always a valid ISO 4217
|
|
14
|
-
* code (enforced by `money()`), the mismatch check ensures `rate.from` must equal a valid code.
|
|
15
|
-
* If you supply an invalid code in `rate.from`, you will receive `CurrencyMismatchError`, not
|
|
16
|
-
* `InvalidCurrencyError`. Pre-validate `rate.from` with `validateCurrencyCode()` if needed.
|
|
17
|
-
* @throws {InvalidCurrencyError} If `rate.to` is not a recognised ISO 4217 currency code.
|
|
18
|
-
* @throws {CoinsError} If `rate.rate` is an empty string, non-numeric, or a negative value.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const usd = money('1000.00', 'USD');
|
|
23
|
-
* exchange(usd, { from: 'USD', to: 'EUR', rate: '0.85' });
|
|
24
|
-
* // { amount: 85000n, currency: 'EUR' }
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
export declare function exchange(m: Money, rate: ExchangeRate, mode?: RoundingMode): Money;
|
|
1
|
+
import type { Currency, ExchangeRate, Money, RoundingMode } from './types';
|
|
2
|
+
export declare function exchangeRate<From extends Currency, To extends Currency>({ from, to, value, }: {
|
|
3
|
+
from: From;
|
|
4
|
+
to: To;
|
|
5
|
+
value: string;
|
|
6
|
+
}): ExchangeRate<From, To>;
|
|
7
|
+
export declare function exchange<From extends Currency, To extends Currency>(value: Money<From>, rate: ExchangeRate<From, To>, options?: {
|
|
8
|
+
rounding?: RoundingMode;
|
|
9
|
+
}): Money<To>;
|
|
28
10
|
//# sourceMappingURL=exchange.d.ts.map
|
package/dist/exchange.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.d.ts","sourceRoot":"","sources":["../src/exchange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"exchange.d.ts","sourceRoot":"","sources":["../src/exchange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAO3E,wBAAgB,YAAY,CAAC,IAAI,SAAS,QAAQ,EAAE,EAAE,SAAS,QAAQ,EAAE,EACvE,IAAI,EACJ,EAAE,EACF,KAAK,GACN,EAAE;IACD,IAAI,EAAE,IAAI,CAAC;IACX,EAAE,EAAE,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CASzB;AAED,wBAAgB,QAAQ,CAAC,IAAI,SAAS,QAAQ,EAAE,EAAE,SAAS,QAAQ,EACjE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,EAC5B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,YAAY,CAAA;CAAO,GACxC,KAAK,CAAC,EAAE,CAAC,CAUX"}
|
package/dist/exchange.js
CHANGED
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
import { CoinsError as e, CurrencyMismatchError as t } from "./errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { decimal as n, roundDivision as r } from "./decimal.js";
|
|
3
|
+
import { isCurrency as i } from "./currency.js";
|
|
4
|
+
import { isMoney as a, withMinor as o } from "./money.js";
|
|
3
5
|
//#region src/exchange.ts
|
|
4
|
-
function
|
|
5
|
-
if (
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
function s({ from: t, to: r, value: a }) {
|
|
7
|
+
if (!i(t) || !i(r)) throw new e("INVALID_CURRENCY", "Exchange rate requires registered currencies");
|
|
8
|
+
let o = n(a);
|
|
9
|
+
if (o.numerator < 0n) throw new e("INVALID_DECIMAL", "Exchange rates cannot be negative");
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
from: t,
|
|
12
|
+
to: r,
|
|
13
|
+
value: o
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function c(n, i, s = {}) {
|
|
17
|
+
if (!a(n) || !l(i)) throw new e("INVALID_MONEY", "Exchange requires canonical money and rate values");
|
|
18
|
+
if (n.currency !== i.from) throw new t(n.currency.code, i.from.code);
|
|
19
|
+
let c = n.amount * i.value.numerator * 10n ** BigInt(i.to.minorUnit), u = i.value.denominator * 10n ** BigInt(i.from.minorUnit);
|
|
20
|
+
return o(r(c, u, s.rounding ?? "halfAwayFromZero"), i.to);
|
|
21
|
+
}
|
|
22
|
+
function l(e) {
|
|
23
|
+
if (typeof e != "object" || !e || !Object.isFrozen(e)) return !1;
|
|
24
|
+
let t = e;
|
|
25
|
+
return i(t.from) && i(t.to) && typeof t.value == "object" && t.value !== null && Object.isFrozen(t.value) && typeof t.value.numerator == "bigint" && t.value.numerator >= 0n && typeof t.value.denominator == "bigint" && t.value.denominator > 0n;
|
|
14
26
|
}
|
|
15
27
|
//#endregion
|
|
16
|
-
export {
|
|
28
|
+
export { c as exchange, s as exchangeRate };
|
|
17
29
|
|
|
18
30
|
//# sourceMappingURL=exchange.js.map
|
package/dist/exchange.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.js","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["import type { ExchangeRate, Money, RoundingMode } from './types';\n\nimport {
|
|
1
|
+
{"version":3,"file":"exchange.js","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["import type { Currency, ExchangeRate, Money, RoundingMode } from './types';\n\nimport { isCurrency } from './currency';\nimport { decimal, roundDivision } from './decimal';\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport { isMoney, withMinor } from './money';\n\nexport function exchangeRate<From extends Currency, To extends Currency>({\n from,\n to,\n value,\n}: {\n from: From;\n to: To;\n value: string;\n}): ExchangeRate<From, To> {\n if (!isCurrency(from) || !isCurrency(to))\n throw new CoinsError('INVALID_CURRENCY', 'Exchange rate requires registered currencies');\n\n const parsed = decimal(value);\n\n if (parsed.numerator < 0n) throw new CoinsError('INVALID_DECIMAL', 'Exchange rates cannot be negative');\n\n return Object.freeze({ from, to, value: parsed });\n}\n\nexport function exchange<From extends Currency, To extends Currency>(\n value: Money<From>,\n rate: ExchangeRate<From, To>,\n options: { rounding?: RoundingMode } = {},\n): Money<To> {\n if (!isMoney(value) || !isValidRate(rate))\n throw new CoinsError('INVALID_MONEY', 'Exchange requires canonical money and rate values');\n\n if (value.currency !== rate.from) throw new CurrencyMismatchError(value.currency.code, rate.from.code);\n\n const numerator = value.amount * rate.value.numerator * 10n ** BigInt(rate.to.minorUnit);\n const denominator = rate.value.denominator * 10n ** BigInt(rate.from.minorUnit);\n\n return withMinor(roundDivision(numerator, denominator, options.rounding ?? 'halfAwayFromZero'), rate.to);\n}\n\nfunction isValidRate(value: unknown): value is ExchangeRate {\n if (typeof value !== 'object' || value === null || !Object.isFrozen(value)) return false;\n\n const rate = value as Partial<ExchangeRate>;\n\n return (\n isCurrency(rate.from) &&\n isCurrency(rate.to) &&\n typeof rate.value === 'object' &&\n rate.value !== null &&\n Object.isFrozen(rate.value) &&\n typeof rate.value.numerator === 'bigint' &&\n rate.value.numerator >= 0n &&\n typeof rate.value.denominator === 'bigint' &&\n rate.value.denominator > 0n\n );\n}\n"],"mappings":";;;;;AAOA,SAAgB,EAAyD,EACvE,SACA,OACA,YAKyB;CACzB,IAAI,CAAC,EAAW,CAAI,KAAK,CAAC,EAAW,CAAE,GACrC,MAAM,IAAI,EAAW,oBAAoB,8CAA8C;CAEzF,IAAM,IAAS,EAAQ,CAAK;CAE5B,IAAI,EAAO,YAAY,IAAI,MAAM,IAAI,EAAW,mBAAmB,mCAAmC;CAEtG,OAAO,OAAO,OAAO;EAAE;EAAM;EAAI,OAAO;CAAO,CAAC;AAClD;AAEA,SAAgB,EACd,GACA,GACA,IAAuC,CAAC,GAC7B;CACX,IAAI,CAAC,EAAQ,CAAK,KAAK,CAAC,EAAY,CAAI,GACtC,MAAM,IAAI,EAAW,iBAAiB,mDAAmD;CAE3F,IAAI,EAAM,aAAa,EAAK,MAAM,MAAM,IAAI,EAAsB,EAAM,SAAS,MAAM,EAAK,KAAK,IAAI;CAErG,IAAM,IAAY,EAAM,SAAS,EAAK,MAAM,YAAY,OAAO,OAAO,EAAK,GAAG,SAAS,GACjF,IAAc,EAAK,MAAM,cAAc,OAAO,OAAO,EAAK,KAAK,SAAS;CAE9E,OAAO,EAAU,EAAc,GAAW,GAAa,EAAQ,YAAY,kBAAkB,GAAG,EAAK,EAAE;AACzG;AAEA,SAAS,EAAY,GAAuC;CAC1D,IAAI,OAAO,KAAU,aAAY,KAAkB,CAAC,OAAO,SAAS,CAAK,GAAG,OAAO;CAEnF,IAAM,IAAO;CAEb,OACE,EAAW,EAAK,IAAI,KACpB,EAAW,EAAK,EAAE,KAClB,OAAO,EAAK,SAAU,YACtB,EAAK,UAAU,QACf,OAAO,SAAS,EAAK,KAAK,KAC1B,OAAO,EAAK,MAAM,aAAc,YAChC,EAAK,MAAM,aAAa,MACxB,OAAO,EAAK,MAAM,eAAgB,YAClC,EAAK,MAAM,cAAc;AAE7B"}
|
package/dist/format.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./decimal.cjs"),n=require("./money.cjs");var r=20,i=new Map,a=new Map;function o(e,t={}){return s(e,t).map(e=>e.value).join(``)}function s(r,i={}){if(!n.isMoney(r))throw new e.CoinsError(`INVALID_MONEY`,`format() requires canonical money`);let{locale:a=`en-US`,maximumFractionDigits:o=r.currency.minorUnit,minimumFractionDigits:s=r.currency.minorUnit,style:d=`symbol`}=i;c(s,o);let f=10n**BigInt(o),p=o>=r.currency.minorUnit?r.amount*10n**BigInt(o-r.currency.minorUnit):t.roundDivision(r.amount,10n**BigInt(r.currency.minorUnit-o),`halfAwayFromZero`),m=p<0n,h=m?-p:p,g=h/f,_=(o===0?``:(h%f).toString().padStart(o,`0`)).replace(RegExp(`0{0,${Math.max(0,o-s)}}$`),``),v=l(a,r.currency.code,d,m),y=[],b=!1;for(let e of v)e.type!==`group`&&(e.type===`integer`?b||=(y.push({type:`integer`,value:u(a).format(g)}),!0):e.type===`decimal`?_&&y.push({type:`decimal`,value:e.value}):e.type===`fraction`?_&&y.push({type:`fraction`,value:_}):e.type===`currency`||e.type===`minusSign`||e.type===`plusSign`?y.push({type:e.type,value:e.value}):y.push({type:`literal`,value:e.value}));return y}function c(t,n){if(!Number.isInteger(n)||!Number.isInteger(t)||t<0||n<t||n>r)throw new e.CoinsError(`INVALID_ROUNDING`,`Fraction digits must be integers satisfying 0 ≤ minimum ≤ maximum ≤ ${r}`)}function l(t,n,r,i){let o=`${t}\0${n}\0${r}\0${i}`,s=a.get(o);if(s)return s;try{let e=new Intl.NumberFormat(t,{currency:n,currencyDisplay:r,maximumFractionDigits:1,minimumFractionDigits:1,style:`currency`}).formatToParts(i?-1.1:1.1);return a.set(o,e),e}catch(r){throw new e.CoinsError(`INVALID_CURRENCY`,`Cannot format currency "${n}" for locale "${t}"`,{cause:r})}}function u(t){let n=i.get(t);if(n)return n;try{let e=new Intl.NumberFormat(t,{maximumFractionDigits:0,useGrouping:!0});return i.set(t,e),e}catch(n){throw new e.CoinsError(`INVALID_ROUNDING`,`Cannot create number formatter for locale "${t}"`,{cause:n})}}exports.format=o,exports.formatParts=s;
|
|
2
2
|
//# sourceMappingURL=format.cjs.map
|
package/dist/format.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { FormatOptions, Money, MoneyFormatPart } from './types';\n\nimport {
|
|
1
|
+
{"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { FormatOptions, Money, MoneyFormatPart } from './types';\n\nimport { roundDivision } from './decimal';\nimport { CoinsError } from './errors';\nimport { isMoney } from './money';\n\nconst MAX_FRACTION_DIGITS = 20;\nconst integerFormatters = new Map<string, Intl.NumberFormat>();\nconst templates = new Map<string, Intl.NumberFormatPart[]>();\n\nexport function format(value: Money, options: FormatOptions = {}): string {\n return formatParts(value, options)\n .map((part) => part.value)\n .join('');\n}\n\nexport function formatParts(value: Money, options: FormatOptions = {}): MoneyFormatPart[] {\n if (!isMoney(value)) throw new CoinsError('INVALID_MONEY', 'format() requires canonical money');\n\n const {\n locale = 'en-US',\n maximumFractionDigits = value.currency.minorUnit,\n minimumFractionDigits = value.currency.minorUnit,\n style = 'symbol',\n } = options;\n\n validateFractionDigits(minimumFractionDigits, maximumFractionDigits);\n\n const targetScale = 10n ** BigInt(maximumFractionDigits);\n const scaled =\n maximumFractionDigits >= value.currency.minorUnit\n ? value.amount * 10n ** BigInt(maximumFractionDigits - value.currency.minorUnit)\n : roundDivision(\n value.amount,\n 10n ** BigInt(value.currency.minorUnit - maximumFractionDigits),\n 'halfAwayFromZero',\n );\n const negative = scaled < 0n;\n const absolute = negative ? -scaled : scaled;\n const integer = absolute / targetScale;\n const rawFraction =\n maximumFractionDigits === 0 ? '' : (absolute % targetScale).toString().padStart(maximumFractionDigits, '0');\n const fraction = rawFraction.replace(\n new RegExp(`0{0,${Math.max(0, maximumFractionDigits - minimumFractionDigits)}}$`),\n '',\n );\n const template = getTemplate(locale, value.currency.code, style, negative);\n const parts: MoneyFormatPart[] = [];\n let insertedInteger = false;\n\n for (const part of template) {\n if (part.type === 'group') continue;\n\n if (part.type === 'integer') {\n if (!insertedInteger) {\n parts.push({ type: 'integer', value: getIntegerFormatter(locale).format(integer) });\n insertedInteger = true;\n }\n } else if (part.type === 'decimal') {\n if (fraction) parts.push({ type: 'decimal', value: part.value });\n } else if (part.type === 'fraction') {\n if (fraction) parts.push({ type: 'fraction', value: fraction });\n } else if (part.type === 'currency' || part.type === 'minusSign' || part.type === 'plusSign') {\n parts.push({ type: part.type, value: part.value });\n } else {\n parts.push({ type: 'literal', value: part.value });\n }\n }\n\n return parts;\n}\n\nfunction validateFractionDigits(minimum: number, maximum: number): void {\n if (\n !Number.isInteger(maximum) ||\n !Number.isInteger(minimum) ||\n minimum < 0 ||\n maximum < minimum ||\n maximum > MAX_FRACTION_DIGITS\n ) {\n throw new CoinsError(\n 'INVALID_ROUNDING',\n `Fraction digits must be integers satisfying 0 ≤ minimum ≤ maximum ≤ ${MAX_FRACTION_DIGITS}`,\n );\n }\n}\n\nfunction getTemplate(\n locale: string,\n currency: string,\n style: NonNullable<FormatOptions['style']>,\n negative: boolean,\n): Intl.NumberFormatPart[] {\n const key = `${locale}\\0${currency}\\0${style}\\0${negative}`;\n const cached = templates.get(key);\n\n if (cached) return cached;\n\n try {\n const template = new Intl.NumberFormat(locale, {\n currency,\n currencyDisplay: style,\n maximumFractionDigits: 1,\n minimumFractionDigits: 1,\n style: 'currency',\n }).formatToParts(negative ? -1.1 : 1.1);\n\n templates.set(key, template);\n\n return template;\n } catch (error) {\n throw new CoinsError('INVALID_CURRENCY', `Cannot format currency \"${currency}\" for locale \"${locale}\"`, {\n cause: error,\n });\n }\n}\n\nfunction getIntegerFormatter(locale: string): Intl.NumberFormat {\n const cached = integerFormatters.get(locale);\n\n if (cached) return cached;\n\n try {\n const formatter = new Intl.NumberFormat(locale, { maximumFractionDigits: 0, useGrouping: true });\n\n integerFormatters.set(locale, formatter);\n\n return formatter;\n } catch (error) {\n throw new CoinsError('INVALID_ROUNDING', `Cannot create number formatter for locale \"${locale}\"`, { cause: error });\n }\n}\n"],"mappings":"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"}
|
package/dist/format.d.ts
CHANGED
|
@@ -1,41 +1,4 @@
|
|
|
1
1
|
import type { FormatOptions, Money, MoneyFormatPart } from './types';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Uses bigint arithmetic throughout — no floating-point precision loss.
|
|
5
|
-
*
|
|
6
|
-
* When `maximumFractionDigits` is less than the currency's native decimal
|
|
7
|
-
* places, the amount is rescaled using `'half-away-from-zero'` rounding.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* const price = money('1234.56', 'USD');
|
|
12
|
-
*
|
|
13
|
-
* format(price) // '$1,234.56'
|
|
14
|
-
* format(price, { locale: 'de-DE' }) // '1.234,56 $'
|
|
15
|
-
* format(price, { style: 'code' }) // 'USD 1,234.56'
|
|
16
|
-
* format(price, { style: 'name' }) // '1,234.56 US dollars'
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
export declare function format(m: Money, options?: FormatOptions): string;
|
|
20
|
-
/**
|
|
21
|
-
* Returns the formatted parts of a `Money` value as a structured array.
|
|
22
|
-
* Useful for custom rendering — apply different styles to each semantic segment
|
|
23
|
-
* (currency symbol, integer, decimal separator, fraction, sign).
|
|
24
|
-
*
|
|
25
|
-
* Joining all `value` fields produces the same string as `format()`.
|
|
26
|
-
* When `maximumFractionDigits` reduces precision, rescaling uses
|
|
27
|
-
* `'half-away-from-zero'` rounding.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* formatParts(money('1234.56', 'USD'))
|
|
32
|
-
* // [
|
|
33
|
-
* // { type: 'currency', value: '$' },
|
|
34
|
-
* // { type: 'integer', value: '1,234' },
|
|
35
|
-
* // { type: 'decimal', value: '.' },
|
|
36
|
-
* // { type: 'fraction', value: '56' },
|
|
37
|
-
* // ]
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export declare function formatParts(m: Money, options?: FormatOptions): MoneyFormatPart[];
|
|
2
|
+
export declare function format(value: Money, options?: FormatOptions): string;
|
|
3
|
+
export declare function formatParts(value: Money, options?: FormatOptions): MoneyFormatPart[];
|
|
41
4
|
//# sourceMappingURL=format.d.ts.map
|
package/dist/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAUrE,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAIxE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB,GAAG,eAAe,EAAE,CAsDxF"}
|
package/dist/format.js
CHANGED
|
@@ -1,104 +1,67 @@
|
|
|
1
1
|
import { CoinsError as e } from "./errors.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { roundDivision as t } from "./decimal.js";
|
|
3
|
+
import { isMoney as n } from "./money.js";
|
|
4
4
|
//#region src/format.ts
|
|
5
|
-
var
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
if (g > h) throw new e("minimumFractionDigits must be less than or equal to maximumFractionDigits");
|
|
9
|
-
let _ = d(t.amount, l, h), v = _ < 0n, y = v ? -_ : _, b = i(h), x = y / b, S = f(h === 0 ? "" : (y % b).toString().padStart(h, "0"), g), C = p(a, t.currency, c, v);
|
|
10
|
-
return {
|
|
11
|
-
frac: S,
|
|
12
|
-
intStr: m(a).format(x),
|
|
13
|
-
negative: v,
|
|
14
|
-
template: C
|
|
15
|
-
};
|
|
5
|
+
var r = 20, i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map();
|
|
6
|
+
function o(e, t = {}) {
|
|
7
|
+
return s(e, t).map((e) => e.value).join("");
|
|
16
8
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
9
|
+
function s(r, i = {}) {
|
|
10
|
+
if (!n(r)) throw new e("INVALID_MONEY", "format() requires canonical money");
|
|
11
|
+
let { locale: a = "en-US", maximumFractionDigits: o = r.currency.minorUnit, minimumFractionDigits: s = r.currency.minorUnit, style: d = "symbol" } = i;
|
|
12
|
+
c(s, o);
|
|
13
|
+
let f = 10n ** BigInt(o), p = o >= r.currency.minorUnit ? r.amount * 10n ** BigInt(o - r.currency.minorUnit) : t(r.amount, 10n ** BigInt(r.currency.minorUnit - o), "halfAwayFromZero"), m = p < 0n, h = m ? -p : p, g = h / f, _ = (o === 0 ? "" : (h % f).toString().padStart(o, "0")).replace(RegExp(`0{0,${Math.max(0, o - s)}}$`), ""), v = l(a, r.currency.code, d, m), y = [], b = !1;
|
|
14
|
+
for (let e of v) e.type !== "group" && (e.type === "integer" ? b ||= (y.push({
|
|
15
|
+
type: "integer",
|
|
16
|
+
value: u(a).format(g)
|
|
17
|
+
}), !0) : e.type === "decimal" ? _ && y.push({
|
|
18
|
+
type: "decimal",
|
|
19
|
+
value: e.value
|
|
20
|
+
}) : e.type === "fraction" ? _ && y.push({
|
|
21
|
+
type: "fraction",
|
|
22
|
+
value: _
|
|
23
|
+
}) : e.type === "currency" || e.type === "minusSign" || e.type === "plusSign" ? y.push({
|
|
24
|
+
type: e.type,
|
|
25
|
+
value: e.value
|
|
26
|
+
}) : y.push({
|
|
27
|
+
type: "literal",
|
|
28
|
+
value: e.value
|
|
29
|
+
}));
|
|
30
|
+
return y;
|
|
19
31
|
}
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
for (let e of i) if (e.type !== "group") {
|
|
23
|
-
if (e.type === "integer") {
|
|
24
|
-
c ||= (o.push({
|
|
25
|
-
type: "integer",
|
|
26
|
-
value: r
|
|
27
|
-
}), !0);
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (e.type === "decimal") {
|
|
31
|
-
a && o.push({
|
|
32
|
-
type: "decimal",
|
|
33
|
-
value: e.value
|
|
34
|
-
});
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (e.type === "fraction") {
|
|
38
|
-
a && o.push({
|
|
39
|
-
type: "fraction",
|
|
40
|
-
value: n
|
|
41
|
-
});
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
e.type === "currency" ? o.push({
|
|
45
|
-
type: "currency",
|
|
46
|
-
value: e.value
|
|
47
|
-
}) : e.type === "minusSign" ? o.push({
|
|
48
|
-
type: "minusSign",
|
|
49
|
-
value: e.value
|
|
50
|
-
}) : o.push({
|
|
51
|
-
type: "literal",
|
|
52
|
-
value: e.value
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return o;
|
|
56
|
-
}
|
|
57
|
-
function u(t, n, r) {
|
|
58
|
-
if (n == null) return r;
|
|
59
|
-
if (!Number.isInteger(n) || n < 0) throw new e(`${t} must be a non-negative integer`);
|
|
60
|
-
return n;
|
|
61
|
-
}
|
|
62
|
-
function d(e, t, r) {
|
|
63
|
-
if (t === r) return e;
|
|
64
|
-
if (r > t) return e * i(r - t);
|
|
65
|
-
let a = e < 0n, o = a ? -e : e, s = i(t - r), c = n(o / s, o % s, s, "half-away-from-zero");
|
|
66
|
-
return a ? -c : c;
|
|
32
|
+
function c(t, n) {
|
|
33
|
+
if (!Number.isInteger(n) || !Number.isInteger(t) || t < 0 || n < t || n > r) throw new e("INVALID_ROUNDING", `Fraction digits must be integers satisfying 0 ≤ minimum ≤ maximum ≤ ${r}`);
|
|
67
34
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
currency: t,
|
|
84
|
-
currencyDisplay: n,
|
|
85
|
-
maximumFractionDigits: 1,
|
|
86
|
-
minimumFractionDigits: 1,
|
|
87
|
-
style: "currency"
|
|
88
|
-
}).formatToParts(r ? -1.1 : 1.1);
|
|
89
|
-
return a.set(i, s), s;
|
|
35
|
+
function l(t, n, r, i) {
|
|
36
|
+
let o = `${t}\0${n}\0${r}\0${i}`, s = a.get(o);
|
|
37
|
+
if (s) return s;
|
|
38
|
+
try {
|
|
39
|
+
let e = new Intl.NumberFormat(t, {
|
|
40
|
+
currency: n,
|
|
41
|
+
currencyDisplay: r,
|
|
42
|
+
maximumFractionDigits: 1,
|
|
43
|
+
minimumFractionDigits: 1,
|
|
44
|
+
style: "currency"
|
|
45
|
+
}).formatToParts(i ? -1.1 : 1.1);
|
|
46
|
+
return a.set(o, e), e;
|
|
47
|
+
} catch (r) {
|
|
48
|
+
throw new e("INVALID_CURRENCY", `Cannot format currency "${n}" for locale "${t}"`, { cause: r });
|
|
49
|
+
}
|
|
90
50
|
}
|
|
91
|
-
function
|
|
92
|
-
let
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
51
|
+
function u(t) {
|
|
52
|
+
let n = i.get(t);
|
|
53
|
+
if (n) return n;
|
|
54
|
+
try {
|
|
55
|
+
let e = new Intl.NumberFormat(t, {
|
|
56
|
+
maximumFractionDigits: 0,
|
|
57
|
+
useGrouping: !0
|
|
58
|
+
});
|
|
59
|
+
return i.set(t, e), e;
|
|
60
|
+
} catch (n) {
|
|
61
|
+
throw new e("INVALID_ROUNDING", `Cannot create number formatter for locale "${t}"`, { cause: n });
|
|
62
|
+
}
|
|
100
63
|
}
|
|
101
64
|
//#endregion
|
|
102
|
-
export {
|
|
65
|
+
export { o as format, s as formatParts };
|
|
103
66
|
|
|
104
67
|
//# sourceMappingURL=format.js.map
|
package/dist/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { FormatOptions, Money, MoneyFormatPart } from './types';\n\nimport {
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { FormatOptions, Money, MoneyFormatPart } from './types';\n\nimport { roundDivision } from './decimal';\nimport { CoinsError } from './errors';\nimport { isMoney } from './money';\n\nconst MAX_FRACTION_DIGITS = 20;\nconst integerFormatters = new Map<string, Intl.NumberFormat>();\nconst templates = new Map<string, Intl.NumberFormatPart[]>();\n\nexport function format(value: Money, options: FormatOptions = {}): string {\n return formatParts(value, options)\n .map((part) => part.value)\n .join('');\n}\n\nexport function formatParts(value: Money, options: FormatOptions = {}): MoneyFormatPart[] {\n if (!isMoney(value)) throw new CoinsError('INVALID_MONEY', 'format() requires canonical money');\n\n const {\n locale = 'en-US',\n maximumFractionDigits = value.currency.minorUnit,\n minimumFractionDigits = value.currency.minorUnit,\n style = 'symbol',\n } = options;\n\n validateFractionDigits(minimumFractionDigits, maximumFractionDigits);\n\n const targetScale = 10n ** BigInt(maximumFractionDigits);\n const scaled =\n maximumFractionDigits >= value.currency.minorUnit\n ? value.amount * 10n ** BigInt(maximumFractionDigits - value.currency.minorUnit)\n : roundDivision(\n value.amount,\n 10n ** BigInt(value.currency.minorUnit - maximumFractionDigits),\n 'halfAwayFromZero',\n );\n const negative = scaled < 0n;\n const absolute = negative ? -scaled : scaled;\n const integer = absolute / targetScale;\n const rawFraction =\n maximumFractionDigits === 0 ? '' : (absolute % targetScale).toString().padStart(maximumFractionDigits, '0');\n const fraction = rawFraction.replace(\n new RegExp(`0{0,${Math.max(0, maximumFractionDigits - minimumFractionDigits)}}$`),\n '',\n );\n const template = getTemplate(locale, value.currency.code, style, negative);\n const parts: MoneyFormatPart[] = [];\n let insertedInteger = false;\n\n for (const part of template) {\n if (part.type === 'group') continue;\n\n if (part.type === 'integer') {\n if (!insertedInteger) {\n parts.push({ type: 'integer', value: getIntegerFormatter(locale).format(integer) });\n insertedInteger = true;\n }\n } else if (part.type === 'decimal') {\n if (fraction) parts.push({ type: 'decimal', value: part.value });\n } else if (part.type === 'fraction') {\n if (fraction) parts.push({ type: 'fraction', value: fraction });\n } else if (part.type === 'currency' || part.type === 'minusSign' || part.type === 'plusSign') {\n parts.push({ type: part.type, value: part.value });\n } else {\n parts.push({ type: 'literal', value: part.value });\n }\n }\n\n return parts;\n}\n\nfunction validateFractionDigits(minimum: number, maximum: number): void {\n if (\n !Number.isInteger(maximum) ||\n !Number.isInteger(minimum) ||\n minimum < 0 ||\n maximum < minimum ||\n maximum > MAX_FRACTION_DIGITS\n ) {\n throw new CoinsError(\n 'INVALID_ROUNDING',\n `Fraction digits must be integers satisfying 0 ≤ minimum ≤ maximum ≤ ${MAX_FRACTION_DIGITS}`,\n );\n }\n}\n\nfunction getTemplate(\n locale: string,\n currency: string,\n style: NonNullable<FormatOptions['style']>,\n negative: boolean,\n): Intl.NumberFormatPart[] {\n const key = `${locale}\\0${currency}\\0${style}\\0${negative}`;\n const cached = templates.get(key);\n\n if (cached) return cached;\n\n try {\n const template = new Intl.NumberFormat(locale, {\n currency,\n currencyDisplay: style,\n maximumFractionDigits: 1,\n minimumFractionDigits: 1,\n style: 'currency',\n }).formatToParts(negative ? -1.1 : 1.1);\n\n templates.set(key, template);\n\n return template;\n } catch (error) {\n throw new CoinsError('INVALID_CURRENCY', `Cannot format currency \"${currency}\" for locale \"${locale}\"`, {\n cause: error,\n });\n }\n}\n\nfunction getIntegerFormatter(locale: string): Intl.NumberFormat {\n const cached = integerFormatters.get(locale);\n\n if (cached) return cached;\n\n try {\n const formatter = new Intl.NumberFormat(locale, { maximumFractionDigits: 0, useGrouping: true });\n\n integerFormatters.set(locale, formatter);\n\n return formatter;\n } catch (error) {\n throw new CoinsError('INVALID_ROUNDING', `Cannot create number formatter for locale \"${locale}\"`, { cause: error });\n }\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./decimal.cjs"),n=require("./currency.cjs"),r=require("./money.cjs"),i=require("./aggregate.cjs"),a=require("./exchange.cjs"),o=require("./format.cjs"),s=require("./serialization.cjs");exports.BHD=n.BHD,exports.CoinsError=e.CoinsError,exports.CurrencyMismatchError=e.CurrencyMismatchError,exports.EUR=n.EUR,exports.GBP=n.GBP,exports.InvalidCurrencyError=e.InvalidCurrencyError,exports.JPY=n.JPY,exports.KRW=n.KRW,exports.KWD=n.KWD,exports.USD=n.USD,exports.abs=r.abs,exports.add=r.add,exports.allocate=i.allocate,exports.clamp=i.clamp,exports.compare=r.compare,exports.currency=n.currency,exports.decimal=t.decimal,exports.defineCurrency=n.defineCurrency,exports.divide=r.divide,exports.exchange=a.exchange,exports.exchangeRate=a.exchangeRate,exports.format=o.format,exports.formatParts=o.formatParts,exports.isCurrency=n.isCurrency,exports.isMoney=r.isMoney,exports.money=r.money,exports.multiply=r.multiply,exports.negate=r.negate,exports.parseMoney=r.parseMoney,exports.parseMoneyJSON=s.parseMoneyJSON,exports.round=r.round,exports.subtract=r.subtract,exports.sum=i.sum,exports.toDecimal=r.toDecimal,exports.toJSON=s.toJSON;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
export { clamp, allocate, sum } from './aggregate';
|
|
2
|
+
export { BHD, currency, defineCurrency, EUR, GBP, isCurrency, JPY, KRW, KWD, USD } from './currency';
|
|
3
|
+
export { decimal } from './decimal';
|
|
1
4
|
export { CoinsError, CurrencyMismatchError, InvalidCurrencyError } from './errors';
|
|
2
|
-
export {
|
|
5
|
+
export type { CoinsErrorCode } from './errors';
|
|
6
|
+
export { exchange, exchangeRate } from './exchange';
|
|
3
7
|
export { format, formatParts } from './format';
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export type { CurrencyCode, ExchangeRate, FormatOptions, Money, MoneyFormatPart, MoneyJSON, RoundingMode, } from './types';
|
|
8
|
+
export { abs, add, compare, divide, isMoney, money, multiply, negate, parseMoney, round, subtract, toDecimal, } from './money';
|
|
9
|
+
export { parseMoneyJSON, toJSON } from './serialization';
|
|
10
|
+
export type { Currency, CurrencyCode, Decimal, ExchangeRate, FormatOptions, Money, MoneyFormatPart, MoneyJSON, RoundingMode, } from './types';
|
|
7
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACrG,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACnF,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EACL,GAAG,EACH,GAAG,EACH,OAAO,EACP,MAAM,EACN,OAAO,EACP,KAAK,EACL,QAAQ,EACR,MAAM,EACN,UAAU,EACV,KAAK,EACL,QAAQ,EACR,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzD,YAAY,EACV,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,YAAY,EACZ,aAAa,EACb,KAAK,EACL,eAAe,EACf,SAAS,EACT,YAAY,GACb,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { CoinsError as e, CurrencyMismatchError as t, InvalidCurrencyError as n } from "./errors.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { decimal as r } from "./decimal.js";
|
|
3
|
+
import { BHD as i, EUR as a, GBP as o, JPY as s, KRW as c, KWD as l, USD as u, currency as d, defineCurrency as f, isCurrency as p } from "./currency.js";
|
|
4
|
+
import { abs as m, add as h, compare as g, divide as _, isMoney as v, money as y, multiply as b, negate as x, parseMoney as S, round as C, subtract as w, toDecimal as T } from "./money.js";
|
|
5
|
+
import { allocate as E, clamp as D, sum as O } from "./aggregate.js";
|
|
6
|
+
import { exchange as k, exchangeRate as A } from "./exchange.js";
|
|
7
|
+
import { format as j, formatParts as M } from "./format.js";
|
|
8
|
+
import { parseMoneyJSON as N, toJSON as P } from "./serialization.js";
|
|
9
|
+
export { i as BHD, e as CoinsError, t as CurrencyMismatchError, a as EUR, o as GBP, n as InvalidCurrencyError, s as JPY, c as KRW, l as KWD, u as USD, m as abs, h as add, E as allocate, D as clamp, g as compare, d as currency, r as decimal, f as defineCurrency, _ as divide, k as exchange, A as exchangeRate, j as format, M as formatParts, p as isCurrency, v as isMoney, y as money, b as multiply, x as negate, S as parseMoney, N as parseMoneyJSON, C as round, w as subtract, O as sum, T as toDecimal, P as toJSON };
|