@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.
Files changed (80) hide show
  1. package/README.md +50 -222
  2. package/dist/aggregate.cjs +2 -0
  3. package/dist/aggregate.cjs.map +1 -0
  4. package/dist/aggregate.d.ts +11 -0
  5. package/dist/aggregate.d.ts.map +1 -0
  6. package/dist/aggregate.js +44 -0
  7. package/dist/aggregate.js.map +1 -0
  8. package/dist/coins.cjs +1 -1
  9. package/dist/coins.cjs.map +1 -1
  10. package/dist/coins.iife.js +1 -1
  11. package/dist/coins.iife.js.map +1 -1
  12. package/dist/coins.js +1 -1
  13. package/dist/coins.js.map +1 -1
  14. package/dist/currency.cjs +2 -0
  15. package/dist/currency.cjs.map +1 -0
  16. package/dist/currency.d.ts +38 -0
  17. package/dist/currency.d.ts.map +1 -0
  18. package/dist/currency.js +55 -0
  19. package/dist/currency.js.map +1 -0
  20. package/dist/decimal.cjs +2 -0
  21. package/dist/decimal.cjs.map +1 -0
  22. package/dist/decimal.d.ts +7 -0
  23. package/dist/decimal.d.ts.map +1 -0
  24. package/dist/decimal.js +52 -0
  25. package/dist/decimal.js.map +1 -0
  26. package/dist/errors.cjs +1 -1
  27. package/dist/errors.cjs.map +1 -1
  28. package/dist/errors.d.ts +6 -42
  29. package/dist/errors.d.ts.map +1 -1
  30. package/dist/errors.js +7 -6
  31. package/dist/errors.js.map +1 -1
  32. package/dist/exchange.cjs +1 -1
  33. package/dist/exchange.cjs.map +1 -1
  34. package/dist/exchange.d.ts +9 -27
  35. package/dist/exchange.d.ts.map +1 -1
  36. package/dist/exchange.js +24 -12
  37. package/dist/exchange.js.map +1 -1
  38. package/dist/format.cjs +1 -1
  39. package/dist/format.cjs.map +1 -1
  40. package/dist/format.d.ts +2 -39
  41. package/dist/format.d.ts.map +1 -1
  42. package/dist/format.js +57 -94
  43. package/dist/format.js.map +1 -1
  44. package/dist/index.cjs +1 -1
  45. package/dist/index.d.ts +8 -4
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +8 -5
  48. package/dist/money.cjs +1 -1
  49. package/dist/money.cjs.map +1 -1
  50. package/dist/money.d.ts +26 -245
  51. package/dist/money.d.ts.map +1 -1
  52. package/dist/money.js +71 -184
  53. package/dist/money.js.map +1 -1
  54. package/dist/serialization.cjs +2 -0
  55. package/dist/serialization.cjs.map +1 -0
  56. package/dist/serialization.d.ts +6 -0
  57. package/dist/serialization.d.ts.map +1 -0
  58. package/dist/serialization.js +45 -0
  59. package/dist/serialization.js.map +1 -0
  60. package/dist/types.d.ts +35 -52
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +3 -3
  63. package/dist/_cache.cjs +0 -2
  64. package/dist/_cache.cjs.map +0 -1
  65. package/dist/_cache.d.ts +0 -6
  66. package/dist/_cache.d.ts.map +0 -1
  67. package/dist/_cache.js +0 -14
  68. package/dist/_cache.js.map +0 -1
  69. package/dist/_dev.cjs +0 -2
  70. package/dist/_dev.cjs.map +0 -1
  71. package/dist/_dev.d.ts +0 -2
  72. package/dist/_dev.d.ts.map +0 -1
  73. package/dist/_dev.js +0 -9
  74. package/dist/_dev.js.map +0 -1
  75. package/dist/utils.cjs +0 -2
  76. package/dist/utils.cjs.map +0 -1
  77. package/dist/utils.d.ts +0 -66
  78. package/dist/utils.d.ts.map +0 -1
  79. package/dist/utils.js +0 -66
  80. package/dist/utils.js.map +0 -1
package/dist/exchange.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./utils.cjs");function n(n,r,i=`half-away-from-zero`){if(n.currency!==r.from)throw new e.CurrencyMismatchError(n.currency,r.from);if(r.rate===``)throw new e.CoinsError(`Exchange rate must be a non-empty decimal string`);let{denominator:a,negative:o,numerator:s}=t.parseRational(String(r.rate));if(o)throw new e.CoinsError(`Exchange rate must be non-negative`);let c=n.amount<0n,l=(n.amount<0n?-n.amount:n.amount)*s,u=t.applyRounding(l/a,l%a,a,i,c);return{amount:c?-u:u,currency:t.validateCurrencyCode(r.to)}}exports.exchange=n;
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
@@ -1 +1 @@
1
- {"version":3,"file":"exchange.cjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["import type { ExchangeRate, Money, RoundingMode } from './types';\n\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport { applyRounding, parseRational, validateCurrencyCode } from './utils';\n\n/**\n * Converts a `Money` value to another currency using the provided exchange rate.\n * Uses lossless bigint arithmetic throughout — `rate.rate` is parsed as a decimal\n * string to avoid IEEE-754 rounding errors. A number `rate.rate` is converted via\n * `String()` first (for symmetry with `multiply()`/`divide()`); prefer a string when\n * precision matters.\n *\n * @param mode Rounding mode applied when the converted amount is not a whole minor unit.\n * Defaults to `'half-away-from-zero'`.\n *\n * @throws {CurrencyMismatchError} If `money.currency` does not match `rate.from`.\n * Note: `rate.from` is validated implicitly — because `money.currency` is always a valid ISO 4217\n * code (enforced by `money()`), the mismatch check ensures `rate.from` must equal a valid code.\n * If you supply an invalid code in `rate.from`, you will receive `CurrencyMismatchError`, not\n * `InvalidCurrencyError`. Pre-validate `rate.from` with `validateCurrencyCode()` if needed.\n * @throws {InvalidCurrencyError} If `rate.to` is not a recognised ISO 4217 currency code.\n * @throws {CoinsError} If `rate.rate` is an empty string, non-numeric, or a negative value.\n *\n * @example\n * ```ts\n * const usd = money('1000.00', 'USD');\n * exchange(usd, { from: 'USD', to: 'EUR', rate: '0.85' });\n * // { amount: 85000n, currency: 'EUR' }\n * ```\n */\nexport function exchange(m: Money, rate: ExchangeRate, mode: RoundingMode = 'half-away-from-zero'): Money {\n if (m.currency !== rate.from) {\n throw new CurrencyMismatchError(m.currency, rate.from);\n }\n\n if (rate.rate === '') throw new CoinsError('Exchange rate must be a non-empty decimal string');\n\n const { denominator, negative: rateNegative, numerator } = parseRational(String(rate.rate));\n\n if (rateNegative) throw new CoinsError('Exchange rate must be non-negative');\n\n const negative = m.amount < 0n;\n const absAmount = m.amount < 0n ? -m.amount : m.amount;\n const raw = absAmount * numerator;\n const quotient = raw / denominator;\n const result = applyRounding(quotient, raw % denominator, denominator, mode, negative);\n\n return { amount: negative ? -result : result, currency: validateCurrencyCode(rate.to) };\n}\n"],"mappings":"yDA8BA,SAAgB,EAAS,EAAU,EAAoB,EAAqB,sBAA8B,CACxG,GAAI,EAAE,WAAa,EAAK,KACtB,MAAM,IAAI,EAAA,sBAAsB,EAAE,SAAU,EAAK,IAAI,EAGvD,GAAI,EAAK,OAAS,GAAI,MAAM,IAAI,EAAA,WAAW,kDAAkD,EAE7F,GAAM,CAAE,cAAa,SAAU,EAAc,aAAc,EAAA,cAAc,OAAO,EAAK,IAAI,CAAC,EAE1F,GAAI,EAAc,MAAM,IAAI,EAAA,WAAW,oCAAoC,EAE3E,IAAM,EAAW,EAAE,OAAS,GAEtB,GADY,EAAE,OAAS,GAAK,CAAC,EAAE,OAAS,EAAE,QACxB,EAElB,EAAS,EAAA,cADE,EAAM,EACgB,EAAM,EAAa,EAAa,EAAM,CAAQ,EAErF,MAAO,CAAE,OAAQ,EAAW,CAAC,EAAS,EAAQ,SAAU,EAAA,qBAAqB,EAAK,EAAE,CAAE,CACxF"}
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"}
@@ -1,28 +1,10 @@
1
- import type { ExchangeRate, Money, RoundingMode } from './types';
2
- /**
3
- * Converts a `Money` value to another currency using the provided exchange rate.
4
- * Uses lossless bigint arithmetic throughout — `rate.rate` is parsed as a decimal
5
- * string to avoid IEEE-754 rounding errors. A number `rate.rate` is converted via
6
- * `String()` first (for symmetry with `multiply()`/`divide()`); prefer a string when
7
- * precision matters.
8
- *
9
- * @param mode Rounding mode applied when the converted amount is not a whole minor unit.
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
@@ -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;AAKjE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,GAAE,YAAoC,GAAG,KAAK,CAkBxG"}
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 { applyRounding as n, parseRational as r, validateCurrencyCode as i } from "./utils.js";
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 a(a, o, s = "half-away-from-zero") {
5
- if (a.currency !== o.from) throw new t(a.currency, o.from);
6
- if (o.rate === "") throw new e("Exchange rate must be a non-empty decimal string");
7
- let { denominator: c, negative: l, numerator: u } = r(String(o.rate));
8
- if (l) throw new e("Exchange rate must be non-negative");
9
- let d = a.amount < 0n, f = (a.amount < 0n ? -a.amount : a.amount) * u, p = n(f / c, f % c, c, s, d);
10
- return {
11
- amount: d ? -p : p,
12
- currency: i(o.to)
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 { a as exchange };
28
+ export { c as exchange, s as exchangeRate };
17
29
 
18
30
  //# sourceMappingURL=exchange.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exchange.js","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["import type { ExchangeRate, Money, RoundingMode } from './types';\n\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport { applyRounding, parseRational, validateCurrencyCode } from './utils';\n\n/**\n * Converts a `Money` value to another currency using the provided exchange rate.\n * Uses lossless bigint arithmetic throughout — `rate.rate` is parsed as a decimal\n * string to avoid IEEE-754 rounding errors. A number `rate.rate` is converted via\n * `String()` first (for symmetry with `multiply()`/`divide()`); prefer a string when\n * precision matters.\n *\n * @param mode Rounding mode applied when the converted amount is not a whole minor unit.\n * Defaults to `'half-away-from-zero'`.\n *\n * @throws {CurrencyMismatchError} If `money.currency` does not match `rate.from`.\n * Note: `rate.from` is validated implicitly — because `money.currency` is always a valid ISO 4217\n * code (enforced by `money()`), the mismatch check ensures `rate.from` must equal a valid code.\n * If you supply an invalid code in `rate.from`, you will receive `CurrencyMismatchError`, not\n * `InvalidCurrencyError`. Pre-validate `rate.from` with `validateCurrencyCode()` if needed.\n * @throws {InvalidCurrencyError} If `rate.to` is not a recognised ISO 4217 currency code.\n * @throws {CoinsError} If `rate.rate` is an empty string, non-numeric, or a negative value.\n *\n * @example\n * ```ts\n * const usd = money('1000.00', 'USD');\n * exchange(usd, { from: 'USD', to: 'EUR', rate: '0.85' });\n * // { amount: 85000n, currency: 'EUR' }\n * ```\n */\nexport function exchange(m: Money, rate: ExchangeRate, mode: RoundingMode = 'half-away-from-zero'): Money {\n if (m.currency !== rate.from) {\n throw new CurrencyMismatchError(m.currency, rate.from);\n }\n\n if (rate.rate === '') throw new CoinsError('Exchange rate must be a non-empty decimal string');\n\n const { denominator, negative: rateNegative, numerator } = parseRational(String(rate.rate));\n\n if (rateNegative) throw new CoinsError('Exchange rate must be non-negative');\n\n const negative = m.amount < 0n;\n const absAmount = m.amount < 0n ? -m.amount : m.amount;\n const raw = absAmount * numerator;\n const quotient = raw / denominator;\n const result = applyRounding(quotient, raw % denominator, denominator, mode, negative);\n\n return { amount: negative ? -result : result, currency: validateCurrencyCode(rate.to) };\n}\n"],"mappings":";;;AA8BA,SAAgB,EAAS,GAAU,GAAoB,IAAqB,uBAA8B;CACxG,IAAI,EAAE,aAAa,EAAK,MACtB,MAAM,IAAI,EAAsB,EAAE,UAAU,EAAK,IAAI;CAGvD,IAAI,EAAK,SAAS,IAAI,MAAM,IAAI,EAAW,kDAAkD;CAE7F,IAAM,EAAE,gBAAa,UAAU,GAAc,iBAAc,EAAc,OAAO,EAAK,IAAI,CAAC;CAE1F,IAAI,GAAc,MAAM,IAAI,EAAW,oCAAoC;CAE3E,IAAM,IAAW,EAAE,SAAS,IAEtB,KADY,EAAE,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,UACxB,GAElB,IAAS,EADE,IAAM,GACgB,IAAM,GAAa,GAAa,GAAM,CAAQ;CAErF,OAAO;EAAE,QAAQ,IAAW,CAAC,IAAS;EAAQ,UAAU,EAAqB,EAAK,EAAE;CAAE;AACxF"}
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("./_cache.cjs"),n=require("./utils.cjs");var r=t.boundedCache(512),i=t.boundedCache(32);function a(t,r){let{locale:i=`en-US`,maximumFractionDigits:a,minimumFractionDigits:o,style:s=`symbol`}=r,p=n.getCurrencyDecimals(t.currency),m=c(`maximumFractionDigits`,a,p),h=c(`minimumFractionDigits`,o,Math.min(p,m));if(h>m)throw new e.CoinsError(`minimumFractionDigits must be less than or equal to maximumFractionDigits`);let g=l(t.amount,p,m),_=g<0n,v=_?-g:g,y=n.pow10(m),b=v/y,x=u(m===0?``:(v%y).toString().padStart(m,`0`),h),S=d(i,t.currency,s,_);return{frac:x,intStr:f(i).format(b),negative:_,template:S}}function o(e,t={}){return s(e,t).map(e=>e.value).join(``)}function s(e,t={}){let{frac:n,intStr:r,template:i}=a(e,t),o=n.length>0,s=[],c=!1;for(let e of i)if(e.type!==`group`){if(e.type===`integer`){c||=(s.push({type:`integer`,value:r}),!0);continue}if(e.type===`decimal`){o&&s.push({type:`decimal`,value:e.value});continue}if(e.type===`fraction`){o&&s.push({type:`fraction`,value:n});continue}e.type===`currency`?s.push({type:`currency`,value:e.value}):e.type===`minusSign`?s.push({type:`minusSign`,value:e.value}):s.push({type:`literal`,value:e.value})}return s}function c(t,n,r){if(n==null)return r;if(!Number.isInteger(n)||n<0)throw new e.CoinsError(`${t} must be a non-negative integer`);return n}function l(e,t,r){if(t===r)return e;if(r>t)return e*n.pow10(r-t);let i=e<0n,a=i?-e:e,o=n.pow10(t-r),s=n.applyRounding(a/o,a%o,o,`half-away-from-zero`);return i?-s:s}function u(e,t){if(e.length===0)return``;let n=e.length;for(;n>t&&e[n-1]===`0`;)n--;return e.slice(0,n)}function d(e,t,n,i){let a=[e,t,n,i?`neg`:`pos`].join(`\0`),o=r.get(a);if(o!==void 0)return o;let s=new Intl.NumberFormat(e,{currency:t,currencyDisplay:n,maximumFractionDigits:1,minimumFractionDigits:1,style:`currency`}).formatToParts(i?-1.1:1.1);return r.set(a,s),s}function f(e){let t=i.get(e);if(t!==void 0)return t;let n=new Intl.NumberFormat(e,{maximumFractionDigits:0,minimumFractionDigits:0,useGrouping:!0});return i.set(e,n),n}exports.format=o,exports.formatParts=s;
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
@@ -1 +1 @@
1
- {"version":3,"file":"format.cjs","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { FormatOptions, Money, MoneyFormatPart } from './types';\n\nimport { boundedCache } from './_cache';\nimport { CoinsError } from './errors';\nimport { applyRounding, getCurrencyDecimals, pow10 } from './utils';\n\n// Template cache key: locale × currency × style × sign — 512 prevents thrashing in multi-locale apps.\nconst currencyTemplateCache = boundedCache<string, Intl.NumberFormatPart[]>(512);\n// Locale-only key — 32 covers virtually every realistic multi-locale deployment.\nconst integerFormatterCache = boundedCache<string, Intl.NumberFormat>(32);\n\n/**\n * Resolves and validates all formatting parameters, performs scaling, and\n * produces the pre-computed strings shared by `format()` and `formatParts()`.\n * Throws on invalid options. New options only need to be added in one place.\n */\ntype FormatState = {\n frac: string;\n intStr: string;\n negative: boolean;\n template: Intl.NumberFormatPart[];\n};\n\nfunction resolveFormatState(m: Money, options: FormatOptions): FormatState {\n const { locale = 'en-US', maximumFractionDigits, minimumFractionDigits, style = 'symbol' } = options;\n\n const decimalPlaces = getCurrencyDecimals(m.currency);\n const maxFrac = validateFractionDigits('maximumFractionDigits', maximumFractionDigits, decimalPlaces);\n const minFrac = validateFractionDigits(\n 'minimumFractionDigits',\n minimumFractionDigits,\n Math.min(decimalPlaces, maxFrac),\n );\n\n if (minFrac > maxFrac) {\n throw new CoinsError('minimumFractionDigits must be less than or equal to maximumFractionDigits');\n }\n\n const scaled = rescaleMinorUnits(m.amount, decimalPlaces, maxFrac);\n const negative = scaled < 0n;\n const abs = negative ? -scaled : scaled;\n const divisor = pow10(maxFrac);\n const whole = abs / divisor;\n const rawFrac = maxFrac === 0 ? '' : (abs % divisor).toString().padStart(maxFrac, '0');\n const frac = trimFraction(rawFrac, minFrac);\n const template = getCurrencyTemplate(locale, m.currency, style, negative);\n const intStr = getIntegerFormatter(locale).format(whole);\n\n return { frac, intStr, negative, template };\n}\n\n/**\n * Formats a `Money` value as a locale-aware currency string.\n * Uses bigint arithmetic throughout — no floating-point precision loss.\n *\n * When `maximumFractionDigits` is less than the currency's native decimal\n * places, the amount is rescaled using `'half-away-from-zero'` rounding.\n *\n * @example\n * ```ts\n * const price = money('1234.56', 'USD');\n *\n * format(price) // '$1,234.56'\n * format(price, { locale: 'de-DE' }) // '1.234,56 $'\n * format(price, { style: 'code' }) // 'USD 1,234.56'\n * format(price, { style: 'name' }) // '1,234.56 US dollars'\n * ```\n */\nexport function format(m: Money, options: FormatOptions = {}): string {\n return formatParts(m, options)\n .map((p) => p.value)\n .join('');\n}\n\n/**\n * Returns the formatted parts of a `Money` value as a structured array.\n * Useful for custom rendering — apply different styles to each semantic segment\n * (currency symbol, integer, decimal separator, fraction, sign).\n *\n * Joining all `value` fields produces the same string as `format()`.\n * When `maximumFractionDigits` reduces precision, rescaling uses\n * `'half-away-from-zero'` rounding.\n *\n * @example\n * ```ts\n * formatParts(money('1234.56', 'USD'))\n * // [\n * // { type: 'currency', value: '$' },\n * // { type: 'integer', value: '1,234' },\n * // { type: 'decimal', value: '.' },\n * // { type: 'fraction', value: '56' },\n * // ]\n * ```\n */\nexport function formatParts(m: Money, options: FormatOptions = {}): MoneyFormatPart[] {\n const { frac, intStr, template } = resolveFormatState(m, options);\n const hasFrac = frac.length > 0;\n const parts: MoneyFormatPart[] = [];\n let replacedInteger = false;\n\n for (const part of template) {\n // Group separators (e.g. comma in '1,234') are already embedded in intStr by\n // getIntegerFormatter(), so the template's group parts are intentionally discarded.\n if (part.type === 'group') continue;\n\n if (part.type === 'integer') {\n if (!replacedInteger) {\n parts.push({ type: 'integer', value: intStr });\n replacedInteger = true;\n }\n\n continue;\n }\n\n if (part.type === 'decimal') {\n if (hasFrac) parts.push({ type: 'decimal', value: part.value });\n\n continue;\n }\n\n if (part.type === 'fraction') {\n if (hasFrac) parts.push({ type: 'fraction', value: frac });\n\n continue;\n }\n\n if (part.type === 'currency') {\n parts.push({ type: 'currency', value: part.value });\n } else if (part.type === 'minusSign') {\n parts.push({ type: 'minusSign', value: part.value });\n } else {\n parts.push({ type: 'literal', value: part.value });\n }\n }\n\n return parts;\n}\n\nfunction validateFractionDigits(name: string, value: number | undefined, fallback: number): number {\n if (value == null) return fallback;\n\n if (!Number.isInteger(value) || value < 0) {\n throw new CoinsError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/**\n * Rescales minor units from `sourceFrac` decimal places to `targetFrac`,\n * using half-away-from-zero rounding when reducing precision.\n * Always normalizes to absolute value before rounding and reapplies the sign after.\n */\nfunction rescaleMinorUnits(amount: bigint, sourceFrac: number, targetFrac: number): bigint {\n if (sourceFrac === targetFrac) return amount;\n\n if (targetFrac > sourceFrac) return amount * pow10(targetFrac - sourceFrac);\n\n const negative = amount < 0n;\n const abs = negative ? -amount : amount;\n const factor = pow10(sourceFrac - targetFrac);\n const quotient = abs / factor;\n const result = applyRounding(quotient, abs % factor, factor, 'half-away-from-zero');\n\n return negative ? -result : result;\n}\n\nfunction trimFraction(value: string, minDigits: number): string {\n if (value.length === 0) return '';\n\n let end = value.length;\n\n while (end > minDigits && value[end - 1] === '0') end--;\n\n return value.slice(0, end);\n}\n\nfunction getCurrencyTemplate(\n locale: string,\n currencyCode: string,\n style: NonNullable<FormatOptions['style']>,\n negative: boolean,\n): Intl.NumberFormatPart[] {\n const key = [locale, currencyCode, style, negative ? 'neg' : 'pos'].join('\\0');\n const cached = currencyTemplateCache.get(key);\n\n if (cached !== undefined) return cached;\n\n const formatter = new Intl.NumberFormat(locale, {\n currency: currencyCode,\n currencyDisplay: style,\n maximumFractionDigits: 1,\n minimumFractionDigits: 1,\n style: 'currency',\n });\n\n const template = formatter.formatToParts(negative ? -1.1 : 1.1);\n\n currencyTemplateCache.set(key, template);\n\n return template;\n}\n\nfunction getIntegerFormatter(locale: string): Intl.NumberFormat {\n const cached = integerFormatterCache.get(locale);\n\n if (cached !== undefined) return cached;\n\n const formatter = new Intl.NumberFormat(locale, {\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n useGrouping: true,\n });\n\n integerFormatterCache.set(locale, formatter);\n\n return formatter;\n}\n"],"mappings":"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"}
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
- * Formats a `Money` value as a locale-aware currency string.
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
@@ -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;AAmDrE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAIpE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB,GAAG,eAAe,EAAE,CA0CpF"}
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 { boundedCache as t } from "./_cache.js";
3
- import { applyRounding as n, getCurrencyDecimals as r, pow10 as i } from "./utils.js";
2
+ import { roundDivision as t } from "./decimal.js";
3
+ import { isMoney as n } from "./money.js";
4
4
  //#region src/format.ts
5
- var a = t(512), o = t(32);
6
- function s(t, n) {
7
- let { locale: a = "en-US", maximumFractionDigits: o, minimumFractionDigits: s, style: c = "symbol" } = n, l = r(t.currency), h = u("maximumFractionDigits", o, l), g = u("minimumFractionDigits", s, Math.min(l, h));
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 c(e, t = {}) {
18
- return l(e, t).map((e) => e.value).join("");
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 l(e, t = {}) {
21
- let { frac: n, intStr: r, template: i } = s(e, t), a = n.length > 0, o = [], c = !1;
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 f(e, t) {
69
- if (e.length === 0) return "";
70
- let n = e.length;
71
- for (; n > t && e[n - 1] === "0";) n--;
72
- return e.slice(0, n);
73
- }
74
- function p(e, t, n, r) {
75
- let i = [
76
- e,
77
- t,
78
- n,
79
- r ? "neg" : "pos"
80
- ].join("\0"), o = a.get(i);
81
- if (o !== void 0) return o;
82
- let s = new Intl.NumberFormat(e, {
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 m(e) {
92
- let t = o.get(e);
93
- if (t !== void 0) return t;
94
- let n = new Intl.NumberFormat(e, {
95
- maximumFractionDigits: 0,
96
- minimumFractionDigits: 0,
97
- useGrouping: !0
98
- });
99
- return o.set(e, n), n;
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 { c as format, l as formatParts };
65
+ export { o as format, s as formatParts };
103
66
 
104
67
  //# sourceMappingURL=format.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format.js","names":[],"sources":["../src/format.ts"],"sourcesContent":["import type { FormatOptions, Money, MoneyFormatPart } from './types';\n\nimport { boundedCache } from './_cache';\nimport { CoinsError } from './errors';\nimport { applyRounding, getCurrencyDecimals, pow10 } from './utils';\n\n// Template cache key: locale × currency × style × sign — 512 prevents thrashing in multi-locale apps.\nconst currencyTemplateCache = boundedCache<string, Intl.NumberFormatPart[]>(512);\n// Locale-only key — 32 covers virtually every realistic multi-locale deployment.\nconst integerFormatterCache = boundedCache<string, Intl.NumberFormat>(32);\n\n/**\n * Resolves and validates all formatting parameters, performs scaling, and\n * produces the pre-computed strings shared by `format()` and `formatParts()`.\n * Throws on invalid options. New options only need to be added in one place.\n */\ntype FormatState = {\n frac: string;\n intStr: string;\n negative: boolean;\n template: Intl.NumberFormatPart[];\n};\n\nfunction resolveFormatState(m: Money, options: FormatOptions): FormatState {\n const { locale = 'en-US', maximumFractionDigits, minimumFractionDigits, style = 'symbol' } = options;\n\n const decimalPlaces = getCurrencyDecimals(m.currency);\n const maxFrac = validateFractionDigits('maximumFractionDigits', maximumFractionDigits, decimalPlaces);\n const minFrac = validateFractionDigits(\n 'minimumFractionDigits',\n minimumFractionDigits,\n Math.min(decimalPlaces, maxFrac),\n );\n\n if (minFrac > maxFrac) {\n throw new CoinsError('minimumFractionDigits must be less than or equal to maximumFractionDigits');\n }\n\n const scaled = rescaleMinorUnits(m.amount, decimalPlaces, maxFrac);\n const negative = scaled < 0n;\n const abs = negative ? -scaled : scaled;\n const divisor = pow10(maxFrac);\n const whole = abs / divisor;\n const rawFrac = maxFrac === 0 ? '' : (abs % divisor).toString().padStart(maxFrac, '0');\n const frac = trimFraction(rawFrac, minFrac);\n const template = getCurrencyTemplate(locale, m.currency, style, negative);\n const intStr = getIntegerFormatter(locale).format(whole);\n\n return { frac, intStr, negative, template };\n}\n\n/**\n * Formats a `Money` value as a locale-aware currency string.\n * Uses bigint arithmetic throughout — no floating-point precision loss.\n *\n * When `maximumFractionDigits` is less than the currency's native decimal\n * places, the amount is rescaled using `'half-away-from-zero'` rounding.\n *\n * @example\n * ```ts\n * const price = money('1234.56', 'USD');\n *\n * format(price) // '$1,234.56'\n * format(price, { locale: 'de-DE' }) // '1.234,56 $'\n * format(price, { style: 'code' }) // 'USD 1,234.56'\n * format(price, { style: 'name' }) // '1,234.56 US dollars'\n * ```\n */\nexport function format(m: Money, options: FormatOptions = {}): string {\n return formatParts(m, options)\n .map((p) => p.value)\n .join('');\n}\n\n/**\n * Returns the formatted parts of a `Money` value as a structured array.\n * Useful for custom rendering — apply different styles to each semantic segment\n * (currency symbol, integer, decimal separator, fraction, sign).\n *\n * Joining all `value` fields produces the same string as `format()`.\n * When `maximumFractionDigits` reduces precision, rescaling uses\n * `'half-away-from-zero'` rounding.\n *\n * @example\n * ```ts\n * formatParts(money('1234.56', 'USD'))\n * // [\n * // { type: 'currency', value: '$' },\n * // { type: 'integer', value: '1,234' },\n * // { type: 'decimal', value: '.' },\n * // { type: 'fraction', value: '56' },\n * // ]\n * ```\n */\nexport function formatParts(m: Money, options: FormatOptions = {}): MoneyFormatPart[] {\n const { frac, intStr, template } = resolveFormatState(m, options);\n const hasFrac = frac.length > 0;\n const parts: MoneyFormatPart[] = [];\n let replacedInteger = false;\n\n for (const part of template) {\n // Group separators (e.g. comma in '1,234') are already embedded in intStr by\n // getIntegerFormatter(), so the template's group parts are intentionally discarded.\n if (part.type === 'group') continue;\n\n if (part.type === 'integer') {\n if (!replacedInteger) {\n parts.push({ type: 'integer', value: intStr });\n replacedInteger = true;\n }\n\n continue;\n }\n\n if (part.type === 'decimal') {\n if (hasFrac) parts.push({ type: 'decimal', value: part.value });\n\n continue;\n }\n\n if (part.type === 'fraction') {\n if (hasFrac) parts.push({ type: 'fraction', value: frac });\n\n continue;\n }\n\n if (part.type === 'currency') {\n parts.push({ type: 'currency', value: part.value });\n } else if (part.type === 'minusSign') {\n parts.push({ type: 'minusSign', value: part.value });\n } else {\n parts.push({ type: 'literal', value: part.value });\n }\n }\n\n return parts;\n}\n\nfunction validateFractionDigits(name: string, value: number | undefined, fallback: number): number {\n if (value == null) return fallback;\n\n if (!Number.isInteger(value) || value < 0) {\n throw new CoinsError(`${name} must be a non-negative integer`);\n }\n\n return value;\n}\n\n/**\n * Rescales minor units from `sourceFrac` decimal places to `targetFrac`,\n * using half-away-from-zero rounding when reducing precision.\n * Always normalizes to absolute value before rounding and reapplies the sign after.\n */\nfunction rescaleMinorUnits(amount: bigint, sourceFrac: number, targetFrac: number): bigint {\n if (sourceFrac === targetFrac) return amount;\n\n if (targetFrac > sourceFrac) return amount * pow10(targetFrac - sourceFrac);\n\n const negative = amount < 0n;\n const abs = negative ? -amount : amount;\n const factor = pow10(sourceFrac - targetFrac);\n const quotient = abs / factor;\n const result = applyRounding(quotient, abs % factor, factor, 'half-away-from-zero');\n\n return negative ? -result : result;\n}\n\nfunction trimFraction(value: string, minDigits: number): string {\n if (value.length === 0) return '';\n\n let end = value.length;\n\n while (end > minDigits && value[end - 1] === '0') end--;\n\n return value.slice(0, end);\n}\n\nfunction getCurrencyTemplate(\n locale: string,\n currencyCode: string,\n style: NonNullable<FormatOptions['style']>,\n negative: boolean,\n): Intl.NumberFormatPart[] {\n const key = [locale, currencyCode, style, negative ? 'neg' : 'pos'].join('\\0');\n const cached = currencyTemplateCache.get(key);\n\n if (cached !== undefined) return cached;\n\n const formatter = new Intl.NumberFormat(locale, {\n currency: currencyCode,\n currencyDisplay: style,\n maximumFractionDigits: 1,\n minimumFractionDigits: 1,\n style: 'currency',\n });\n\n const template = formatter.formatToParts(negative ? -1.1 : 1.1);\n\n currencyTemplateCache.set(key, template);\n\n return template;\n}\n\nfunction getIntegerFormatter(locale: string): Intl.NumberFormat {\n const cached = integerFormatterCache.get(locale);\n\n if (cached !== undefined) return cached;\n\n const formatter = new Intl.NumberFormat(locale, {\n maximumFractionDigits: 0,\n minimumFractionDigits: 0,\n useGrouping: true,\n });\n\n integerFormatterCache.set(locale, formatter);\n\n return formatter;\n}\n"],"mappings":"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"}
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("./utils.cjs"),n=require("./exchange.cjs"),r=require("./format.cjs"),i=require("./money.cjs");exports.CoinsError=e.CoinsError,exports.CurrencyMismatchError=e.CurrencyMismatchError,exports.InvalidCurrencyError=e.InvalidCurrencyError,exports.abs=i.abs,exports.add=i.add,exports.allocate=i.allocate,exports.clamp=i.clamp,exports.compare=i.compare,exports.divide=i.divide,exports.exchange=n.exchange,exports.format=r.format,exports.formatParts=r.formatParts,exports.fromJSON=i.fromJSON,exports.getCurrencyDecimals=t.getCurrencyDecimals,exports.greaterThan=i.greaterThan,exports.greaterThanOrEqual=i.greaterThanOrEqual,exports.isEqual=i.isEqual,exports.isMoney=i.isMoney,exports.isNegative=i.isNegative,exports.isNonNegative=i.isNonNegative,exports.isNonPositive=i.isNonPositive,exports.isPositive=i.isPositive,exports.isZero=i.isZero,exports.lessThan=i.lessThan,exports.lessThanOrEqual=i.lessThanOrEqual,exports.max=i.max,exports.min=i.min,exports.money=i.money,exports.multiply=i.multiply,exports.negate=i.negate,exports.roundTo=i.roundTo,exports.splitEvenly=i.splitEvenly,exports.subtract=i.subtract,exports.sum=i.sum,exports.toDecimal=i.toDecimal,exports.toJSON=i.toJSON,exports.toNumber=i.toNumber,exports.validateCurrencyCode=t.validateCurrencyCode,exports.withAmount=i.withAmount;
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 { exchange } from './exchange';
5
+ export type { CoinsErrorCode } from './errors';
6
+ export { exchange, exchangeRate } from './exchange';
3
7
  export { format, formatParts } from './format';
4
- export { getCurrencyDecimals, validateCurrencyCode } from './utils';
5
- export { abs, add, allocate, clamp, compare, divide, fromJSON, greaterThan, greaterThanOrEqual, isEqual, isMoney, isNegative, isNonNegative, isNonPositive, isPositive, isZero, lessThan, lessThanOrEqual, max, min, money, multiply, negate, roundTo, splitEvenly, subtract, sum, toDecimal, toJSON, toNumber, withAmount, } from './money';
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACnF,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EACL,GAAG,EACH,GAAG,EACH,QAAQ,EACR,KAAK,EACL,OAAO,EACP,MAAM,EACN,QAAQ,EACR,WAAW,EACX,kBAAkB,EAClB,OAAO,EACP,OAAO,EACP,UAAU,EACV,aAAa,EACb,aAAa,EACb,UAAU,EACV,MAAM,EACN,QAAQ,EACR,eAAe,EACf,GAAG,EACH,GAAG,EACH,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAO,EACP,WAAW,EACX,QAAQ,EACR,GAAG,EACH,SAAS,EACT,MAAM,EACN,QAAQ,EACR,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,KAAK,EACL,eAAe,EACf,SAAS,EACT,YAAY,GACb,MAAM,SAAS,CAAC"}
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 { getCurrencyDecimals as r, validateCurrencyCode as i } from "./utils.js";
3
- import { exchange as a } from "./exchange.js";
4
- import { format as o, formatParts as s } from "./format.js";
5
- import { abs as c, add as l, allocate as u, clamp as d, compare as f, divide as p, fromJSON as m, greaterThan as h, greaterThanOrEqual as g, isEqual as _, isMoney as v, isNegative as y, isNonNegative as b, isNonPositive as x, isPositive as S, isZero as C, lessThan as w, lessThanOrEqual as T, max as E, min as D, money as O, multiply as k, negate as A, roundTo as j, splitEvenly as M, subtract as N, sum as P, toDecimal as F, toJSON as I, toNumber as L, withAmount as R } from "./money.js";
6
- export { e as CoinsError, t as CurrencyMismatchError, n as InvalidCurrencyError, c as abs, l as add, u as allocate, d as clamp, f as compare, p as divide, a as exchange, o as format, s as formatParts, m as fromJSON, r as getCurrencyDecimals, h as greaterThan, g as greaterThanOrEqual, _ as isEqual, v as isMoney, y as isNegative, b as isNonNegative, x as isNonPositive, S as isPositive, C as isZero, w as lessThan, T as lessThanOrEqual, E as max, D as min, O as money, k as multiply, A as negate, j as roundTo, M as splitEvenly, N as subtract, P as sum, F as toDecimal, I as toJSON, L as toNumber, i as validateCurrencyCode, R as withAmount };
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 };