@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
@@ -1 +1 @@
1
- {"version":3,"file":"coins.cjs","names":[],"sources":["../src/errors.ts","../src/_cache.ts","../src/utils.ts","../src/exchange.ts","../src/format.ts","../src/_dev.ts","../src/money.ts"],"sourcesContent":["/** Base class for all coins errors. Use `instanceof CoinsError` to catch any coins-originated error. */\nexport class CoinsError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is CoinsError {\n return err instanceof CoinsError;\n }\n}\n\n/**\n * Thrown when two `Money` values with different currencies are used in an\n * operation that requires the same currency (e.g. `add`, `compare`, `exchange`).\n *\n * @example\n * ```ts\n * import { CurrencyMismatchError } from '@vielzeug/coins';\n *\n * try {\n * add(usd, eur);\n * } catch (e) {\n * if (e instanceof CurrencyMismatchError) {\n * console.log(e.expected, e.received);\n * }\n * }\n * ```\n */\nexport class CurrencyMismatchError extends CoinsError {\n /** The currency of the first operand. */\n readonly expected: string;\n /** The currency of the second (mismatching) operand. */\n readonly received: string;\n\n constructor(expected: string, received: string) {\n super(`Currency mismatch: ${expected} and ${received}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\n/**\n * Thrown when an unrecognised ISO 4217 currency code is passed to `money`,\n * `exchange`, or any function that validates a currency string.\n *\n * @example\n * ```ts\n * import { InvalidCurrencyError } from '@vielzeug/coins';\n *\n * try {\n * money('1.00', 'FAKE');\n * } catch (e) {\n * if (e instanceof InvalidCurrencyError) {\n * console.log('Bad code:', e.code);\n * }\n * }\n * ```\n */\nexport class InvalidCurrencyError extends CoinsError {\n /** The unrecognised currency code that was provided. */\n readonly code: string;\n\n constructor(code: string) {\n super(`Invalid ISO 4217 currency code: \"${code}\"`);\n this.code = code;\n }\n}\n","/** A FIFO bounded Map cache that evicts the oldest entry when full. */\nexport function boundedCache<K, V>(maxSize: number): { get(k: K): V | undefined; set(k: K, v: V): void } {\n const map = new Map<K, V>();\n\n return {\n get: (k) => map.get(k),\n set(k, v) {\n if (map.size >= maxSize) map.delete(map.keys().next().value as K);\n\n map.set(k, v);\n },\n };\n}\n","import type { CurrencyCode, RoundingMode } from './types';\n\nimport { boundedCache } from './_cache';\nimport { CoinsError, InvalidCurrencyError } from './errors';\n\nconst currencyDecimalsCache = boundedCache<string, number>(512);\n\n/**\n * Returns the number of minor-unit decimal places for a given ISO 4217 currency code.\n * Uses `Intl.NumberFormat` to resolve the canonical value (e.g. USD→2, JPY→0, KWD→3).\n * Throws `InvalidCurrencyError` for unrecognized codes or when the runtime cannot determine decimals.\n */\nexport function getCurrencyDecimals(currencyCode: CurrencyCode): number {\n const cached = currencyDecimalsCache.get(currencyCode);\n\n if (cached !== undefined) return cached;\n\n let resolved: number | undefined;\n\n try {\n resolved = new Intl.NumberFormat('en', { currency: currencyCode, style: 'currency' }).resolvedOptions()\n .maximumFractionDigits;\n } catch {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n if (resolved === undefined) {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n currencyDecimalsCache.set(currencyCode, resolved);\n\n return resolved;\n}\n\n/**\n * Validates an ISO 4217 currency code and returns it.\n * Throws `InvalidCurrencyError` if unrecognized. Uses `getCurrencyDecimals` internally so\n * the result is cached for subsequent decimal lookups.\n */\nexport function validateCurrencyCode(code: CurrencyCode): CurrencyCode {\n getCurrencyDecimals(code);\n\n return code;\n}\n\n/** Returns `10n ** BigInt(exponent)`. */\nexport function pow10(exponent: number): bigint {\n return 10n ** BigInt(exponent);\n}\n\n/** Regex for valid decimal strings accepted by `parseRational`. */\nconst DECIMAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/** Regex matching JavaScript scientific-notation strings, e.g. `'1e-7'`, `'-3.14E+5'`. */\nexport const SCIENTIFIC_RE = /^(-?\\d+\\.?\\d*)[eE]([+-]?\\d+)$/;\n\n/** Maximum absolute exponent allowed in scientific notation. Prevents '0'.repeat(N) allocation attacks. */\nconst MAX_SCIENTIFIC_EXP = 1000;\n\n/**\n * Expands a scientific-notation decimal string into a plain decimal string.\n * `'1e-7'` → `'0.0000001'`, `'1.23e+5'` → `'123000'`.\n * Only called when `SCIENTIFIC_RE` already matched — no extra validation.\n * @throws {CoinsError} If the absolute exponent exceeds `MAX_SCIENTIFIC_EXP` (1000).\n */\nexport function expandScientific(s: string): string {\n const match = SCIENTIFIC_RE.exec(s)!;\n const coeff = match[1]!;\n const exp = parseInt(match[2]!, 10);\n\n if (Math.abs(exp) > MAX_SCIENTIFIC_EXP) {\n throw new CoinsError(`Scientific notation exponent too large: \"${s}\" (max ±${MAX_SCIENTIFIC_EXP})`);\n }\n\n const isNeg = coeff.startsWith('-');\n const absCoeff = isNeg ? coeff.slice(1) : coeff;\n const dotIndex = absCoeff.indexOf('.');\n const digits = dotIndex === -1 ? absCoeff : absCoeff.replace('.', '');\n const fracLen = dotIndex === -1 ? 0 : absCoeff.length - dotIndex - 1;\n const newExp = exp - fracLen; // where the decimal point sits relative to end of digits\n\n let result: string;\n\n if (newExp >= 0) {\n // All digits are to the left of the decimal — append trailing zeros\n result = digits + '0'.repeat(newExp);\n } else {\n const intLen = digits.length + newExp; // digits before the decimal point\n\n if (intLen <= 0) {\n // Pure fraction: '0.' + leading zeros + digits\n result = '0.' + '0'.repeat(-intLen) + digits;\n } else {\n result = digits.slice(0, intLen) + '.' + digits.slice(intLen);\n }\n }\n\n return isNeg ? `-${result}` : result;\n}\n\n/**\n * Parses a decimal string into a rational `{ numerator, denominator, negative }`.\n * The denominator is always a power of 10 and the numerator is always non-negative.\n * @throws {CoinsError} For non-numeric input.\n *\n * Accepts standard decimal strings and JavaScript scientific notation\n * (e.g. `'1e-7'`, `'1.23E+5'`) — the latter is automatically expanded before parsing.\n *\n * @example\n * parseRational('1.5') → { numerator: 15n, denominator: 10n, negative: false }\n * parseRational('-0.5') → { numerator: 5n, denominator: 10n, negative: true }\n * parseRational('3') → { numerator: 3n, denominator: 1n, negative: false }\n * parseRational('1e-7') → { numerator: 1n, denominator: 10000000n, negative: false }\n *\n * Note: `'-0'` and `'-0.0'` produce `{ negative: true, numerator: 0n, ... }`.\n * Callers that compute `negative ? -result : result` on `0n` remain correct\n * because `-0n === 0n` in bigint arithmetic.\n */\nexport function parseRational(str: string): { denominator: bigint; negative: boolean; numerator: bigint } {\n // Expand scientific notation (e.g. from String(1e-7) = '1e-7') before validation.\n const normalized = SCIENTIFIC_RE.test(str) ? expandScientific(str) : str;\n\n if (!DECIMAL_RE.test(normalized)) {\n throw new CoinsError(`Invalid decimal string: \"${str}\"`);\n }\n\n const negative = normalized.startsWith('-');\n const absStr = negative ? normalized.slice(1) : normalized;\n const dotIndex = absStr.indexOf('.');\n const intStr = dotIndex === -1 ? absStr : absStr.slice(0, dotIndex);\n const fracStr = dotIndex === -1 ? '' : absStr.slice(dotIndex + 1);\n const denominator = pow10(fracStr.length);\n const numerator = BigInt(intStr || '0') * denominator + BigInt(fracStr || '0');\n\n return { denominator, negative, numerator };\n}\n\n/**\n * Applies a rounding mode to a truncated division result.\n *\n * **Contract**: `quotient` and `absRemainder` must be non-negative (absolute values).\n * The caller is responsible for:\n * 1. Computing everything in terms of absolute values.\n * 2. Passing `negative = true` when the true mathematical result is negative.\n * 3. Applying the sign after: `negative ? -result : result`.\n *\n * Returns the absolute value of the rounded result.\n *\n * @param quotient Non-negative integer part of the division (`|dividend| / divisor`).\n * @param absRemainder Non-negative remainder (`|dividend| % divisor`).\n * @param denominator The divisor used to produce quotient/remainder.\n * @param mode The desired rounding mode.\n * @param negative Whether the true mathematical result is negative. Defaults to `false`.\n */\nexport function applyRounding(\n quotient: bigint,\n absRemainder: bigint,\n denominator: bigint,\n mode: RoundingMode,\n negative = false,\n): bigint {\n if (absRemainder === 0n) return quotient;\n\n switch (mode) {\n case 'ceiling':\n // Toward +∞: round up for positive results, truncate for negative results.\n return negative ? quotient : quotient + 1n;\n\n case 'down':\n // Toward zero: always truncate.\n return quotient;\n\n case 'floor':\n // Toward −∞: truncate for positive results, one step more for negative results.\n return negative ? quotient + 1n : quotient;\n\n case 'half-away-from-zero':\n return absRemainder * 2n >= denominator ? quotient + 1n : quotient;\n\n case 'half-even': {\n const twice = absRemainder * 2n;\n\n if (twice < denominator) return quotient;\n\n if (twice > denominator) return quotient + 1n;\n\n // Exactly half — round to the nearest even integer.\n return quotient % 2n === 0n ? quotient : quotient + 1n;\n }\n\n case 'up':\n // Away from zero: always round away.\n return quotient + 1n;\n\n default:\n throw new CoinsError(`Unknown rounding mode: ${String(mode)}`);\n }\n}\n","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","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","const isDev = !(globalThis as { __COINS_PROD__?: boolean }).__COINS_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/coins] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import type { CurrencyCode, Money, MoneyJSON, RoundingMode } from './types';\n\nimport { warn } from './_dev';\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport {\n applyRounding,\n expandScientific,\n getCurrencyDecimals,\n parseRational,\n pow10,\n SCIENTIFIC_RE,\n validateCurrencyCode,\n} from './utils';\n\nexport { CoinsError, CurrencyMismatchError, InvalidCurrencyError } from './errors';\n\n// ─── Factories ───────────────────────────────────────────────────────────────\n\n/**\n * Creates a `Money` value from a decimal string, number, or raw bigint minor units.\n * Validates the currency code — throws `InvalidCurrencyError` for unrecognised ISO 4217 codes.\n *\n * - **string** `'1234.56'` → parsed losslessly; respects the currency's decimal places.\n * - **number** `1234.56` → converted via `String()` first; inherits IEEE-754 limits.\n * Prefer strings when precision matters.\n * - **bigint** `123456n` → used as-is (already in minor units).\n *\n * @example\n * ```ts\n * money('1234.56', 'USD') // { amount: 123456n, currency: 'USD' }\n * money(1234, 'JPY') // { amount: 1234n, currency: 'JPY' }\n * money(123456n, 'USD') // { amount: 123456n, currency: 'USD' }\n * ```\n */\nexport function money(amount: bigint | number | string, currency: CurrencyCode): Money {\n const validCurrency = validateCurrencyCode(currency);\n\n if (typeof amount === 'bigint') {\n return { amount, currency: validCurrency };\n }\n\n if (typeof amount === 'number') {\n const decimals = getCurrencyDecimals(validCurrency);\n const str = String(amount);\n // Expand scientific notation (e.g. String(1e-7) === '1e-7') before measuring fraction\n // length — otherwise inputs like 1e-7 report 0 fractional digits and skip the warning\n // below despite having far more precision than the currency supports.\n const normalized = SCIENTIFIC_RE.test(str) ? expandScientific(str) : str;\n const dotIndex = normalized.indexOf('.');\n const fracLen = dotIndex === -1 ? 0 : normalized.length - dotIndex - 1;\n\n if (fracLen > decimals) {\n warn(\n `money(): number input \"${amount}\" has more decimal places (${fracLen}) than ${validCurrency} supports (${decimals}). ` +\n `Use a decimal string to avoid IEEE-754 precision loss.`,\n );\n }\n\n // getCurrencyDecimals is cached from the validateCurrencyCode call above.\n return { amount: parseToMinorUnits(str, decimals), currency: validCurrency };\n }\n\n // getCurrencyDecimals is cached from the validateCurrencyCode call above.\n return { amount: parseToMinorUnits(String(amount), getCurrencyDecimals(validCurrency)), currency: validCurrency };\n}\n\n// ─── Internal ─────────────────────────────────────────────────────────────────\n\nfunction assertSameCurrency(a: Money, b: Money): void {\n if (a.currency !== b.currency) {\n throw new CurrencyMismatchError(a.currency, b.currency);\n }\n}\n\n/**\n * Converts a decimal string to minor units for the given decimal precision.\n * Uses parseRational + applyRounding to share the single rounding implementation.\n */\nfunction parseToMinorUnits(str: string, decimals: number): bigint {\n const { denominator, negative, numerator } = parseRational(str);\n // numerator/denominator × 10^decimals = numerator × 10^decimals / denominator\n const scale = pow10(decimals);\n const raw = numerator * scale;\n const quotient = raw / denominator;\n const result = applyRounding(quotient, raw % denominator, denominator, 'half-away-from-zero');\n\n return negative ? -result : result;\n}\n\n/**\n * Creates a new `Money` value with the given `amount` and the same currency as `m`.\n * Useful when you compute a raw `bigint` amount externally and need to wrap it\n * back into a `Money` value without re-validating the currency.\n *\n * @example\n * ```ts\n * const price = money('9.99', 'USD');\n * withAmount(price, 1999n) // { amount: 1999n, currency: 'USD' }\n * ```\n */\nexport function withAmount(m: Money, amount: bigint): Money {\n return { amount, currency: m.currency };\n}\n\n/**\n * Type guard that returns `true` if `value` is a `Money`-shaped object\n * (has a `bigint` `amount` and a `string` `currency`).\n * Useful for narrowing unknown payloads from APIs or deserialized storage.\n *\n * Note: does **not** validate the currency code — use `money(0n, currency)` or\n * `validateCurrencyCode()` if you also need to confirm it is a recognized ISO 4217 code.\n *\n * @example\n * ```ts\n * isMoney({ amount: 100n, currency: 'USD' }) // true\n * isMoney({ amount: 1.5, currency: 'USD' }) // false\n * isMoney(null) // false\n * ```\n */\nexport function isMoney(value: unknown): value is Money {\n if (typeof value !== 'object' || value === null) return false;\n\n const v = value as Record<string, unknown>;\n\n return (\n Object.hasOwn(v, 'amount') &&\n typeof v.amount === 'bigint' &&\n Object.hasOwn(v, 'currency') &&\n typeof v.currency === 'string'\n );\n}\n\n// ─── Arithmetic ──────────────────────────────────────────────────────────────\n\n/** Adds two `Money` values. Throws if their currencies differ. */\nexport function add(a: Money, b: Money): Money {\n assertSameCurrency(a, b);\n\n return { amount: a.amount + b.amount, currency: a.currency };\n}\n\n/** Subtracts `b` from `a`. Throws if their currencies differ. */\nexport function subtract(a: Money, b: Money): Money {\n assertSameCurrency(a, b);\n\n return { amount: a.amount - b.amount, currency: a.currency };\n}\n\n/**\n * Multiplies a `Money` value by a scalar factor.\n * The factor can be a decimal string (lossless) or a number.\n *\n * @param mode Rounding mode for fractional minor units. Defaults to `'half-away-from-zero'`.\n *\n * @example\n * ```ts\n * multiply(money('100.00', 'USD'), '1.5') // $150.00\n * multiply(money('1.00', 'USD'), '0.339', 'floor') // $0.33\n * multiply(money('1.00', 'USD'), '0.339', 'ceiling') // $0.34\n * ```\n */\nexport function multiply(m: Money, factor: number | string, mode: RoundingMode = 'half-away-from-zero'): Money {\n const { denominator, negative: factorNegative, numerator } = parseRational(String(factor));\n const negative = m.amount < 0n !== factorNegative;\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: m.currency };\n}\n\n/**\n * Divides a `Money` value by a scalar divisor.\n * The divisor can be a decimal string (lossless) or a number.\n *\n * @param mode Rounding mode for fractional minor units. Defaults to `'half-away-from-zero'`.\n * @throws {CoinsError} On division by zero.\n *\n * @example\n * ```ts\n * divide(money('100.00', 'USD'), 3) // $33.33\n * divide(money('100.00', 'USD'), 3, 'ceiling') // $33.34\n * ```\n */\nexport function divide(m: Money, divisor: number | string, mode: RoundingMode = 'half-away-from-zero'): Money {\n const { denominator: parsedDenom, negative: divisorNegative, numerator } = parseRational(String(divisor));\n\n if (numerator === 0n) throw new CoinsError('Division by zero');\n\n const negative = m.amount < 0n !== divisorNegative;\n const absAmount = m.amount < 0n ? -m.amount : m.amount;\n // Dividing by (numerator/parsedDenom) = multiplying by (parsedDenom/numerator).\n const raw = absAmount * parsedDenom;\n const quotient = raw / numerator;\n const result = applyRounding(quotient, raw % numerator, numerator, mode, negative);\n\n return { amount: negative ? -result : result, currency: m.currency };\n}\n\n/**\n * Distributes `money` across `ratios` without losing or gaining a single minor unit.\n * Accepts both number and string ratios — use strings for lossless decimal weights.\n *\n * Uses the Largest Remainder Method: each share gets its floor allocation first,\n * then any remainder units are assigned one-by-one to the shares with the largest\n * fractional parts, breaking ties by original index (stable, left-to-right).\n *\n * Throws if `ratios` is empty, contains negative values (including negative strings like `'-0.5'`), or sums to zero.\n *\n * @example\n * ```ts\n * allocate(money('10.00', 'USD'), [1, 1, 1])\n * // → [$3.34, $3.33, $3.33] (not three × $3.33 — that loses a penny)\n *\n * allocate(money('10.00', 'USD'), ['0.3', '0.7'])\n * // → [$3.00, $7.00]\n * ```\n */\nexport function allocate(m: Money, ratios: readonly (number | string)[]): [Money, ...Money[]] {\n if (ratios.length === 0) throw new CoinsError('allocate requires at least one ratio');\n\n const parsedRatios = ratios.map((r) => parseRational(String(r)));\n\n if (parsedRatios.some((p) => p.negative)) throw new CoinsError('All ratios must be non-negative');\n\n if (!parsedRatios.some((p) => p.numerator > 0n)) throw new CoinsError('At least one ratio must be positive');\n\n const negative = m.amount < 0n;\n const absAmount = negative ? -m.amount : m.amount;\n\n // All denominators are powers of 10; the max is their LCM — use it to normalise weights to integers.\n const maxDenominator = parsedRatios.reduce((max, p) => (p.denominator > max ? p.denominator : max), 1n);\n const bigWeights = parsedRatios.map((p) => p.numerator * (maxDenominator / p.denominator));\n const bigTotal = bigWeights.reduce((a, b) => a + b, 0n);\n\n // Floor allocation + fractional remainders.\n const floors: bigint[] = [];\n const fracRemainders: bigint[] = [];\n\n for (const w of bigWeights) {\n const ideal = absAmount * w;\n\n floors.push(ideal / bigTotal);\n fracRemainders.push(ideal % bigTotal);\n }\n\n // Distribute leftover units to the shares with the largest fractional remainders.\n // Relies on V8/SpiderMonkey's stable sort (ECMAScript 2019+ spec-mandated): ties\n // in fractional remainders are broken by original index (left-to-right), ensuring\n // deterministic output for equal-weight ratios (e.g. [1, 1, 1]).\n const indices = ratios\n .map((_, i) => i)\n .sort((a, b) => {\n const diff = fracRemainders[b]! - fracRemainders[a]!;\n\n return diff > 0n ? 1 : diff < 0n ? -1 : 0;\n });\n\n const result = [...floors];\n // `remaining` is always < ratios.length (at most one unit per share), safe for this loop.\n let remaining = absAmount - floors.reduce((a, b) => a + b, 0n);\n\n for (let i = 0; remaining > 0n; i++, remaining--) {\n result[indices[i]!]! += 1n;\n }\n\n return result.map((amount) => ({ amount: negative ? -amount : amount, currency: m.currency })) as [Money, ...Money[]];\n}\n\n// ─── Aggregates ──────────────────────────────────────────────────────────────\n\n/**\n * Sums an array of `Money` values. Throws if the array is empty or currencies differ.\n *\n * @example\n * ```ts\n * sum([money('1.00', 'USD'), money('2.50', 'USD')]) // $3.50\n * ```\n */\nexport function sum(moneys: readonly Money[]): Money {\n if (moneys.length === 0) throw new CoinsError('sum requires at least one Money value');\n\n const currency = moneys[0]!.currency;\n\n for (let i = 1; i < moneys.length; i++) {\n if (moneys[i]!.currency !== currency) {\n throw new CurrencyMismatchError(currency, moneys[i]!.currency);\n }\n }\n\n return { amount: moneys.reduce((acc, m) => acc + m.amount, 0n), currency };\n}\n\n/**\n * Returns the smallest `Money` value from a non-empty array. Throws if currencies differ.\n *\n * @example\n * ```ts\n * min([money('3.00', 'USD'), money('1.00', 'USD'), money('2.00', 'USD')]) // $1.00\n * ```\n */\nexport function min(moneys: readonly Money[]): Money {\n if (moneys.length === 0) throw new CoinsError('min requires at least one Money value');\n\n let result = moneys[0]!;\n\n for (let i = 1; i < moneys.length; i++) result = compare(result, moneys[i]!) <= 0 ? result : moneys[i]!;\n\n return result;\n}\n\n/**\n * Returns the largest `Money` value from a non-empty array. Throws if currencies differ.\n *\n * @example\n * ```ts\n * max([money('1.00', 'USD'), money('3.00', 'USD'), money('2.00', 'USD')]) // $3.00\n * ```\n */\nexport function max(moneys: readonly Money[]): Money {\n if (moneys.length === 0) throw new CoinsError('max requires at least one Money value');\n\n let result = moneys[0]!;\n\n for (let i = 1; i < moneys.length; i++) result = compare(result, moneys[i]!) >= 0 ? result : moneys[i]!;\n\n return result;\n}\n\n/**\n * Splits `money` into `parts` equal shares without losing or gaining a single minor unit.\n * Equivalent to `allocate(money, Array.from({ length: parts }, () => 1))`.\n *\n * @throws {CoinsError} If `parts` is not a positive integer (≥ 1).\n *\n * @example\n * ```ts\n * splitEvenly(money('10.00', 'USD'), 3)\n * // → [$3.34, $3.33, $3.33]\n * ```\n */\nexport function splitEvenly(m: Money, parts: number): [Money, ...Money[]] {\n if (!Number.isInteger(parts) || parts <= 0) {\n throw new CoinsError('splitEvenly requires a positive integer number of parts');\n }\n\n return allocate(\n m,\n Array.from({ length: parts }, () => 1),\n );\n}\n\n/**\n * Clamps `m` to the inclusive range `[lower, upper]`.\n *\n * @throws {CurrencyMismatchError} If `m`, `lower`, or `upper` have different currencies.\n * @throws {CoinsError} If `lower > upper`.\n *\n * @example\n * ```ts\n * clamp(money('5.00', 'USD'), money('1.00', 'USD'), money('10.00', 'USD')) // $5.00\n * clamp(money('0.00', 'USD'), money('1.00', 'USD'), money('10.00', 'USD')) // $1.00\n * clamp(money('15.00', 'USD'), money('1.00', 'USD'), money('10.00', 'USD')) // $10.00\n * ```\n */\nexport function clamp(m: Money, lower: Money, upper: Money): Money {\n // Validate currency consistency upfront so the error always names the mismatch,\n // rather than surfacing mid-computation from min() or max().\n if (m.currency !== lower.currency || m.currency !== upper.currency) {\n throw new CurrencyMismatchError(m.currency, m.currency !== lower.currency ? lower.currency : upper.currency);\n }\n\n if (compare(lower, upper) > 0) {\n throw new CoinsError(\n `clamp: lower (${toDecimal(lower)} ${lower.currency}) must be <= upper (${toDecimal(upper)} ${upper.currency})`,\n );\n }\n\n return max([lower, min([m, upper])]);\n}\n\n// ─── Unary ───────────────────────────────────────────────────────────────────\n\n/** Returns the absolute value of `money`. Negative amounts become positive. */\nexport function abs(m: Money): Money {\n return { amount: m.amount < 0n ? -m.amount : m.amount, currency: m.currency };\n}\n\n/** Returns `money` with its sign flipped. */\nexport function negate(m: Money): Money {\n return { amount: -m.amount, currency: m.currency };\n}\n\n// ─── Comparison ──────────────────────────────────────────────────────────────\n\n/**\n * Compares two `Money` values. Returns `-1`, `0`, or `1`.\n * Throws if their currencies differ.\n */\nexport function compare(a: Money, b: Money): -1 | 0 | 1 {\n assertSameCurrency(a, b);\n\n if (a.amount < b.amount) return -1;\n\n if (a.amount > b.amount) return 1;\n\n return 0;\n}\n\n/**\n * Returns `true` if both `Money` values have the same currency and amount.\n * Returns `false` if currencies differ — safe to use in `.filter()` and conditional chains.\n */\nexport function isEqual(a: Money, b: Money): boolean {\n return a.currency === b.currency && a.amount === b.amount;\n}\n\n/** Returns `true` if `a` is strictly greater than `b`. Throws on currency mismatch. */\nexport function greaterThan(a: Money, b: Money): boolean {\n return compare(a, b) > 0;\n}\n\n/** Returns `true` if `a` is greater than or equal to `b`. Throws on currency mismatch. */\nexport function greaterThanOrEqual(a: Money, b: Money): boolean {\n return compare(a, b) >= 0;\n}\n\n/** Returns `true` if `a` is strictly less than `b`. Throws on currency mismatch. */\nexport function lessThan(a: Money, b: Money): boolean {\n return compare(a, b) < 0;\n}\n\n/** Returns `true` if `a` is less than or equal to `b`. Throws on currency mismatch. */\nexport function lessThanOrEqual(a: Money, b: Money): boolean {\n return compare(a, b) <= 0;\n}\n\n// ─── Predicates ──────────────────────────────────────────────────────────────\n\n/** Returns `true` if the amount is exactly zero. */\nexport function isZero(m: Money): boolean {\n return m.amount === 0n;\n}\n\n/** Returns `true` if the amount is strictly positive (> 0). */\nexport function isPositive(m: Money): boolean {\n return m.amount > 0n;\n}\n\n/** Returns `true` if the amount is strictly negative (< 0). */\nexport function isNegative(m: Money): boolean {\n return m.amount < 0n;\n}\n\n/** Returns `true` if the amount is zero or positive (>= 0). */\nexport function isNonNegative(m: Money): boolean {\n return m.amount >= 0n;\n}\n\n/** Returns `true` if the amount is zero or negative (<= 0). */\nexport function isNonPositive(m: Money): boolean {\n return m.amount <= 0n;\n}\n\n// ─── Serialization ───────────────────────────────────────────────────────────\n\n/**\n * Serializes a `Money` value to a plain-object JSON-safe form.\n * `amount` is a bigint string (e.g. `'123456'`) to avoid `JSON.stringify` throwing.\n *\n * @example\n * ```ts\n * toJSON(money('1234.56', 'USD')) // { amount: '123456', currency: 'USD' }\n * JSON.stringify(toJSON(price)) // '{\"amount\":\"123456\",\"currency\":\"USD\"}'\n * ```\n */\nexport function toJSON(m: Money): MoneyJSON {\n return { amount: String(m.amount), currency: m.currency };\n}\n\n/**\n * Deserializes a `MoneyJSON` object back into a `Money` value.\n * Validates the currency code and the amount string.\n *\n * @example\n * ```ts\n * fromJSON({ amount: '123456', currency: 'USD' }) // { amount: 123456n, currency: 'USD' }\n * ```\n */\nexport function fromJSON(json: MoneyJSON): Money {\n const validCurrency = validateCurrencyCode(json.currency);\n\n if (typeof json.amount !== 'string') {\n throw new CoinsError(\n `Invalid money amount in JSON: ${String(json.amount)} (expected an integer string, e.g. '123456')`,\n );\n }\n\n let amount: bigint;\n\n try {\n amount = BigInt(json.amount);\n } catch {\n throw new CoinsError(`Invalid money amount in JSON: \"${json.amount}\" (expected an integer string, e.g. '123456')`);\n }\n\n return { amount, currency: validCurrency };\n}\n\n/**\n * Serializes a `Money` value to a decimal string (e.g. `'1234.56'`).\n * Round-trips losslessly with `money()`.\n *\n * @example\n * ```ts\n * toDecimal(money('1234.56', 'USD')) // '1234.56'\n * toDecimal(money(5n, 'USD')) // '0.05'\n * toDecimal(money(1234n, 'JPY')) // '1234'\n * ```\n */\nexport function toDecimal(m: Money): string {\n const decimals = getCurrencyDecimals(m.currency);\n\n if (decimals === 0) return String(m.amount);\n\n const negative = m.amount < 0n;\n const abs = negative ? -m.amount : m.amount;\n const scale = pow10(decimals);\n const whole = abs / scale;\n const frac = (abs % scale).toString().padStart(decimals, '0');\n\n return `${negative ? '-' : ''}${whole}.${frac}`;\n}\n\n/**\n * Converts a `Money` value to a floating-point number.\n * Useful for charting and display; **not** for arithmetic (lossy).\n *\n * @example\n * ```ts\n * toNumber(money('1234.56', 'USD')) // 1234.56\n * ```\n */\nexport function toNumber(m: Money): number {\n const decimals = getCurrencyDecimals(m.currency);\n\n return Number(m.amount) / Number(pow10(decimals));\n}\n\n// ─── Rounding ─────────────────────────────────────────────────────────────────\n\n/**\n * Rounds a `Money` value to fewer decimal places than the currency's default.\n * `places` must be a non-negative integer in the range `0..currencyDecimals`.\n *\n * Useful for display purposes (e.g. rounding USD to whole dollars).\n * @throws {CoinsError} If `places` is out of the allowed range.\n *\n * @param mode Rounding mode. Defaults to `'half-away-from-zero'`.\n *\n * @example\n * ```ts\n * roundTo(money('1234.56', 'USD'), 0) // $1235 (whole dollars)\n * roundTo(money('1234.56', 'USD'), 1) // $1234.6\n * roundTo(money('1234.56', 'USD'), 1, 'floor') // $1234.5\n * roundTo(money('1234', 'JPY'), 0) // ¥1234 (no-op: JPY is already 0-decimal)\n * ```\n */\nexport function roundTo(m: Money, places: number, mode: RoundingMode = 'half-away-from-zero'): Money {\n if (!Number.isInteger(places) || places < 0) {\n throw new CoinsError(`roundTo: places must be a non-negative integer, got ${places}`);\n }\n\n const currencyDecimals = getCurrencyDecimals(m.currency);\n\n if (places > currencyDecimals) {\n throw new CoinsError(\n `roundTo: places (${places}) exceeds the decimal places for ${m.currency} (${currencyDecimals})`,\n );\n }\n\n if (places === currencyDecimals) return m;\n\n const drop = currencyDecimals - places;\n const divisor = pow10(drop);\n const negative = m.amount < 0n;\n const absAmount = negative ? -m.amount : m.amount;\n const quotient = absAmount / divisor;\n const remainder = absAmount % divisor;\n const rounded = applyRounding(quotient, remainder, divisor, mode, negative);\n\n return { amount: negative ? -rounded : rounded, currency: m.currency };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"coins.cjs","names":[],"sources":["../src/errors.ts","../src/decimal.ts","../src/currency.ts","../src/money.ts","../src/aggregate.ts","../src/exchange.ts","../src/format.ts","../src/serialization.ts"],"sourcesContent":["export type CoinsErrorCode =\n | 'CURRENCY_MISMATCH'\n | 'DIVISION_BY_ZERO'\n | 'INVALID_ALLOCATION'\n | 'INVALID_CURRENCY'\n | 'INVALID_DECIMAL'\n | 'INVALID_MONEY'\n | 'INVALID_ROUNDING';\n\nexport class CoinsError extends Error {\n readonly code: CoinsErrorCode;\n\n static is(error: unknown): error is CoinsError {\n return error instanceof CoinsError;\n }\n\n constructor(code: CoinsErrorCode, message: string, options?: ErrorOptions) {\n super(message, options);\n this.code = code;\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class CurrencyMismatchError extends CoinsError {\n readonly expected: string;\n readonly received: string;\n\n constructor(expected: string, received: string) {\n super('CURRENCY_MISMATCH', `Currency mismatch: ${expected} and ${received}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\nexport class InvalidCurrencyError extends CoinsError {\n readonly value: unknown;\n\n constructor(value: unknown) {\n super('INVALID_CURRENCY', `Unsupported currency: \"${String(value)}\"`);\n this.value = value;\n }\n}\n","import type { Decimal, RoundingMode } from './types';\n\nimport { CoinsError } from './errors';\n\nconst DECIMAL = /^(-?)(\\d+)(?:\\.(\\d+))?$/;\nconst MAX_DECIMAL_DIGITS = 1000;\nconst MAX_DECIMAL_PLACES = 100;\n\nexport function decimal(value: string): Decimal {\n if (value.length > MAX_DECIMAL_DIGITS) throw new CoinsError('INVALID_DECIMAL', 'Decimal input is too long');\n\n const match = DECIMAL.exec(value);\n\n if (!match) throw new CoinsError('INVALID_DECIMAL', `Invalid decimal: \"${value}\"`);\n\n const fraction = (match[3] ?? '').replace(/0+$/, '');\n\n if (fraction.length > MAX_DECIMAL_PLACES) {\n throw new CoinsError('INVALID_DECIMAL', `Decimal precision cannot exceed ${MAX_DECIMAL_PLACES} places`);\n }\n\n const denominator = 10n ** BigInt(fraction.length);\n const unsigned = BigInt(match[2]!) * denominator + BigInt(fraction || '0');\n const numerator = match[1] === '-' && unsigned !== 0n ? -unsigned : unsigned;\n const divisor = gcd(numerator < 0n ? -numerator : numerator, denominator);\n\n return Object.freeze({ denominator: denominator / divisor, numerator: numerator / divisor }) as Decimal;\n}\n\nexport function roundDivision(numerator: bigint, denominator: bigint, mode: RoundingMode): bigint {\n if (denominator <= 0n) throw new CoinsError('INVALID_DECIMAL', 'Decimal denominator must be positive');\n\n const negative = numerator < 0n;\n const absolute = negative ? -numerator : numerator;\n const quotient = absolute / denominator;\n const remainder = absolute % denominator;\n\n if (remainder === 0n) return negative ? -quotient : quotient;\n\n const increment = (() => {\n switch (mode) {\n case 'awayFromZero':\n return true;\n case 'ceil':\n return !negative;\n case 'floor':\n return negative;\n case 'halfAwayFromZero':\n return remainder * 2n >= denominator;\n case 'halfEven': {\n const doubled = remainder * 2n;\n\n return doubled > denominator || (doubled === denominator && quotient % 2n !== 0n);\n }\n case 'towardZero':\n return false;\n }\n })();\n\n const result = increment ? quotient + 1n : quotient;\n\n return negative ? -result : result;\n}\n\nexport function toDecimalString(amount: bigint, minorUnit: number): string {\n const negative = amount < 0n;\n const absolute = negative ? -amount : amount;\n const scale = 10n ** BigInt(minorUnit);\n const whole = absolute / scale;\n\n if (minorUnit === 0) return `${negative ? '-' : ''}${whole}`;\n\n const fraction = (absolute % scale).toString().padStart(minorUnit, '0');\n\n return `${negative ? '-' : ''}${whole}.${fraction}`;\n}\n\nexport function gcd(left: bigint, right: bigint): bigint {\n let a = left;\n let b = right;\n\n while (b !== 0n) [a, b] = [b, a % b];\n\n return a === 0n ? 1n : a;\n}\n\nexport function lcm(left: bigint, right: bigint): bigint {\n return (left / gcd(left, right)) * right;\n}\n","import type { Currency, CurrencyCode } from './types';\n\nimport { CoinsError, InvalidCurrencyError } from './errors';\n\nconst builtins = new Map<string, Currency>();\nconst custom = new Map<string, Currency>();\n\nfunction builtin<C extends string>(code: C, minorUnit: number): Currency<C> {\n const definition = createDefinition(code, minorUnit);\n\n builtins.set(code, definition);\n\n return definition;\n}\n\nexport const USD = builtin('USD', 2);\nexport const EUR = builtin('EUR', 2);\nexport const GBP = builtin('GBP', 2);\nexport const JPY = builtin('JPY', 0);\nexport const KRW = builtin('KRW', 0);\nexport const BHD = builtin('BHD', 3);\nexport const KWD = builtin('KWD', 3);\n\n/** Custom definitions are explicit process configuration; built-ins remain immutable and separate. */\nexport function defineCurrency<C extends string>({ code, minorUnit }: { code: C; minorUnit: number }): Currency<C> {\n validateDefinition(code, minorUnit);\n\n const builtinDefinition = builtins.get(code);\n\n if (builtinDefinition) {\n if (builtinDefinition.minorUnit !== minorUnit) throw duplicateScaleError(code, builtinDefinition.minorUnit);\n\n return builtinDefinition as Currency<C>;\n }\n\n const existing = custom.get(code);\n\n if (existing) {\n if (existing.minorUnit !== minorUnit) throw duplicateScaleError(code, existing.minorUnit);\n\n return existing as Currency<C>;\n }\n\n const definition = createDefinition(code, minorUnit);\n\n custom.set(code, definition);\n\n return definition;\n}\n\nexport function currency(code: string): Currency {\n const definition = builtins.get(code) ?? custom.get(code);\n\n if (!definition) throw new InvalidCurrencyError(code);\n\n return definition;\n}\n\nexport function isCurrency(value: unknown): value is Currency {\n if (typeof value !== 'object' || value === null) return false;\n\n const candidate = value as Partial<Currency>;\n\n return (\n typeof candidate.code === 'string' &&\n (builtins.get(candidate.code) === value || custom.get(candidate.code) === value)\n );\n}\n\nexport function resolveBuiltinCurrency(code: string): Currency {\n const definition = builtins.get(code);\n\n if (!definition) throw new InvalidCurrencyError(code);\n\n return definition;\n}\n\nfunction createDefinition<C extends string>(code: C, minorUnit: number): Currency<C> {\n validateDefinition(code, minorUnit);\n\n return Object.freeze({ code: code as CurrencyCode<C>, minorUnit }) as Currency<C>;\n}\n\nfunction validateDefinition(code: string, minorUnit: number): void {\n if (!/^[A-Z]{3}$/.test(code)) {\n throw new CoinsError('INVALID_CURRENCY', `Currency code must be three uppercase letters: \"${code}\"`);\n }\n\n if (!Number.isInteger(minorUnit) || minorUnit < 0 || minorUnit > 6) {\n throw new CoinsError('INVALID_CURRENCY', `Currency \"${code}\" must have 0–6 minor-unit digits`);\n }\n}\n\nfunction duplicateScaleError(code: string, minorUnit: number): CoinsError {\n return new CoinsError('INVALID_CURRENCY', `Currency \"${code}\" already has ${minorUnit} minor-unit digits`);\n}\n","import type { Currency, Money, RoundingMode } from './types';\n\nimport { isCurrency } from './currency';\nimport { decimal, roundDivision, toDecimalString } from './decimal';\nimport { CoinsError, CurrencyMismatchError } from './errors';\n\nconst defaultRounding: RoundingMode = 'halfAwayFromZero';\n\nexport function money<C extends Currency>(amount: string, currency: C): Money<C>;\nexport function money<C extends Currency>(amount: string, currency: C, options: { rounding: RoundingMode }): Money<C>;\nexport function money<C extends Currency>(amount: bigint, currency: C, options: { unit: 'minor' }): Money<C>;\nexport function money<C extends Currency>(\n amount: bigint | string,\n currency: C,\n options?: { rounding: RoundingMode } | { unit: 'minor' },\n): Money<C> {\n assertCurrency(currency);\n\n if (typeof amount === 'bigint') {\n if (!options || !('unit' in options) || options.unit !== 'minor') {\n throw new CoinsError('INVALID_MONEY', \"Bigint amounts require { unit: 'minor' }\");\n }\n\n return createMoney(amount, currency);\n }\n\n if (options && 'unit' in options)\n throw new CoinsError('INVALID_MONEY', 'Decimal strings do not accept a unit option');\n\n const value = decimal(amount);\n const scaled = value.numerator * 10n ** BigInt(currency.minorUnit);\n const remainder = scaled % value.denominator;\n\n if (remainder !== 0n && !options) {\n throw new CoinsError('INVALID_MONEY', `Amount \"${amount}\" exceeds ${currency.code} precision; provide rounding`);\n }\n\n return createMoney(roundDivision(scaled, value.denominator, options?.rounding ?? defaultRounding), currency);\n}\n\nexport function parseMoney(value: unknown): Money {\n if (!isPlainDataObject(value)) throw new CoinsError('INVALID_MONEY', 'Money must be a plain data object');\n\n const descriptors = Object.getOwnPropertyDescriptors(value);\n const amountDescriptor = descriptors.amount;\n const currencyDescriptor = descriptors.currency;\n\n if (!isDataProperty(amountDescriptor) || !isDataProperty(currencyDescriptor)) {\n throw new CoinsError('INVALID_MONEY', 'Money properties must be plain data values');\n }\n\n if (typeof amountDescriptor.value !== 'bigint' || !isCurrency(currencyDescriptor.value)) {\n throw new CoinsError('INVALID_MONEY', 'Money must contain bigint amount and a registered currency');\n }\n\n return createMoney(amountDescriptor.value, currencyDescriptor.value);\n}\n\nexport function isMoney(value: unknown): value is Money {\n try {\n parseMoney(value);\n\n return true;\n } catch {\n return false;\n }\n}\n\nexport function add<C extends Currency>(left: Money<C>, right: Money<NoInfer<C>>): Money<C> {\n assertSameCurrency(left, right);\n\n return createMoney(left.amount + right.amount, left.currency);\n}\n\nexport function subtract<C extends Currency>(left: Money<C>, right: Money<NoInfer<C>>): Money<C> {\n assertSameCurrency(left, right);\n\n return createMoney(left.amount - right.amount, left.currency);\n}\n\nexport function multiply<C extends Currency>(\n value: Money<C>,\n factor: string,\n options: { rounding?: RoundingMode } = {},\n): Money<C> {\n assertMoney(value);\n\n const scalar = decimal(factor);\n\n return createMoney(\n roundDivision(value.amount * scalar.numerator, scalar.denominator, options.rounding ?? defaultRounding),\n value.currency,\n );\n}\n\nexport function divide<C extends Currency>(\n value: Money<C>,\n divisor: string,\n options: { rounding?: RoundingMode } = {},\n): Money<C> {\n assertMoney(value);\n\n const scalar = decimal(divisor);\n\n if (scalar.numerator === 0n) throw new CoinsError('DIVISION_BY_ZERO', 'Cannot divide money by zero');\n\n return createMoney(\n roundDivision(\n value.amount * scalar.denominator,\n scalar.numerator < 0n ? -scalar.numerator : scalar.numerator,\n options.rounding ?? defaultRounding,\n ) * (scalar.numerator < 0n ? -1n : 1n),\n value.currency,\n );\n}\n\nexport function compare<C extends Currency>(left: Money<C>, right: Money<NoInfer<C>>): -1 | 0 | 1 {\n assertSameCurrency(left, right);\n\n return left.amount === right.amount ? 0 : left.amount < right.amount ? -1 : 1;\n}\n\nexport function abs<C extends Currency>(value: Money<C>): Money<C> {\n assertMoney(value);\n\n return createMoney(value.amount < 0n ? -value.amount : value.amount, value.currency);\n}\n\nexport function negate<C extends Currency>(value: Money<C>): Money<C> {\n assertMoney(value);\n\n return createMoney(-value.amount, value.currency);\n}\n\nexport function round<C extends Currency>(\n value: Money<C>,\n options: { fractionDigits: number; rounding?: RoundingMode },\n): Money<C> {\n assertMoney(value);\n\n const { fractionDigits, rounding = defaultRounding } = options;\n\n if (!Number.isInteger(fractionDigits) || fractionDigits < 0 || fractionDigits > value.currency.minorUnit) {\n throw new CoinsError('INVALID_ROUNDING', `fractionDigits must be an integer from 0 to ${value.currency.minorUnit}`);\n }\n\n const factor = 10n ** BigInt(value.currency.minorUnit - fractionDigits);\n\n return createMoney(roundDivision(value.amount, factor, rounding) * factor, value.currency);\n}\n\nexport function toDecimal(value: Money): string {\n assertMoney(value);\n\n return toDecimalString(value.amount, value.currency.minorUnit);\n}\n\nexport function withMinor<C extends Currency>(amount: bigint, currency: C): Money<C> {\n assertCurrency(currency);\n\n return createMoney(amount, currency);\n}\n\nfunction createMoney<C extends Currency>(amount: bigint, currency: C): Money<C> {\n return Object.freeze({ amount, currency }) as Money<C>;\n}\n\nfunction assertMoney(value: Money): void {\n if (!Object.isFrozen(value) || !isCurrency(value.currency)) {\n throw new CoinsError('INVALID_MONEY', 'Money must be a canonical Coins value');\n }\n}\n\nfunction assertSameCurrency(left: Money, right: Money): void {\n assertMoney(left);\n assertMoney(right);\n\n if (left.currency !== right.currency) {\n throw new CurrencyMismatchError(left.currency.code, right.currency.code);\n }\n}\n\nfunction assertCurrency(value: Currency): void {\n if (!isCurrency(value)) throw new CoinsError('INVALID_CURRENCY', 'Money requires a registered currency');\n}\n\nfunction isPlainDataObject(value: unknown): value is Record<PropertyKey, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n\n const prototype = Object.getPrototypeOf(value);\n\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction isDataProperty(\n descriptor: PropertyDescriptor | undefined,\n): descriptor is PropertyDescriptor & { value: unknown } {\n return (\n descriptor !== undefined && 'value' in descriptor && descriptor.get === undefined && descriptor.set === undefined\n );\n}\n","import type { Currency, Money } from './types';\n\nimport { decimal, lcm } from './decimal';\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport { compare, isMoney, withMinor } from './money';\n\nexport function sum<C extends Currency>(values: Iterable<Money<C>>, options: { currency: C }): Money<C> {\n let amount = 0n;\n\n for (const value of values) {\n if (!isMoney(value)) throw new CoinsError('INVALID_MONEY', 'sum() requires canonical money values');\n\n if (value.currency !== options.currency)\n throw new CurrencyMismatchError(options.currency.code, value.currency.code);\n\n amount += value.amount;\n }\n\n return withMinor(amount, options.currency);\n}\n\nexport function allocate<C extends Currency>(value: Money<C>, count: number): Money<C>[];\nexport function allocate<C extends Currency>(value: Money<C>, weights: readonly string[]): Money<C>[];\nexport function allocate<C extends Currency>(value: Money<C>, weightsOrCount: number | readonly string[]): Money<C>[] {\n if (!isMoney(value)) throw new CoinsError('INVALID_MONEY', 'allocate() requires canonical money');\n\n if (typeof weightsOrCount === 'number') return allocateEvenly(value, weightsOrCount);\n\n if (weightsOrCount.length === 0) throw new CoinsError('INVALID_ALLOCATION', 'Allocation weights cannot be empty');\n\n const weights = weightsOrCount.map(decimal);\n\n if (weights.some((weight) => weight.numerator < 0n)) {\n throw new CoinsError('INVALID_ALLOCATION', 'Allocation weights cannot be negative');\n }\n\n const commonDenominator = weights.reduce((result, weight) => lcm(result, weight.denominator), 1n);\n const scaledWeights = weights.map((weight) => weight.numerator * (commonDenominator / weight.denominator));\n const totalWeight = scaledWeights.reduce((result, weight) => result + weight, 0n);\n\n if (totalWeight === 0n)\n throw new CoinsError('INVALID_ALLOCATION', 'Allocation weights must include a positive value');\n\n const sign = value.amount < 0n ? -1n : 1n;\n const absolute = value.amount < 0n ? -value.amount : value.amount;\n const shares = scaledWeights.map((weight) => (absolute * weight) / totalWeight);\n let remainder = absolute - shares.reduce((result, amount) => result + amount, 0n);\n const ranked = scaledWeights\n .map((weight, index) => ({ index, remainder: (absolute * weight) % totalWeight }))\n .sort((left, right) =>\n left.remainder === right.remainder ? left.index - right.index : left.remainder > right.remainder ? -1 : 1,\n );\n\n for (const entry of ranked) {\n if (remainder === 0n) break;\n\n shares[entry.index]! += 1n;\n remainder -= 1n;\n }\n\n return shares.map((amount) => withMinor(amount * sign, value.currency));\n}\n\nexport function clamp<C extends Currency>(\n value: Money<C>,\n options: { max: Money<NoInfer<C>>; min: Money<NoInfer<C>> },\n): Money<C> {\n if (compare(options.min, options.max) === 1) {\n throw new CoinsError('INVALID_ALLOCATION', 'Clamp minimum cannot exceed maximum');\n }\n\n return compare(value, options.min) === -1 ? options.min : compare(value, options.max) === 1 ? options.max : value;\n}\n\nfunction allocateEvenly<C extends Currency>(value: Money<C>, countValue: number): Money<C>[] {\n if (!Number.isInteger(countValue) || countValue < 1) {\n throw new CoinsError('INVALID_ALLOCATION', 'Allocation count must be a positive integer');\n }\n\n const count = BigInt(countValue);\n const sign = value.amount < 0n ? -1n : 1n;\n const absolute = value.amount < 0n ? -value.amount : value.amount;\n const base = absolute / count;\n const remainder = absolute % count;\n\n return Array.from({ length: countValue }, (_, index) =>\n withMinor((base + (BigInt(index) < remainder ? 1n : 0n)) * sign, value.currency),\n );\n}\n","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","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","import type { Currency, Money, MoneyJSON } from './types';\n\nimport { resolveBuiltinCurrency } from './currency';\nimport { CoinsError } from './errors';\nimport { money } from './money';\n\nconst INTEGER = /^(?:0|-[1-9]\\d*|[1-9]\\d*)$/;\nconst KEYS = ['amount', 'currency', 'unit'] as const;\n\nexport function toJSON(value: Money): MoneyJSON {\n return { amount: value.amount.toString(), currency: value.currency.code, unit: 'minor' };\n}\n\nexport function parseMoneyJSON(value: unknown, options: { currency?: (code: string) => Currency } = {}): Money {\n try {\n const payload = readPayload(value);\n const resolveCurrency = options.currency ?? resolveBuiltinCurrency;\n\n return money(BigInt(payload.amount), resolveCurrency(payload.currency), { unit: 'minor' });\n } catch (error) {\n throw new CoinsError('INVALID_MONEY', 'Invalid Money JSON', { cause: error });\n }\n}\n\nfunction readPayload(value: unknown): MoneyJSON {\n if (typeof value !== 'object' || value === null || Object.getPrototypeOf(value) !== Object.prototype) {\n throw new TypeError('Money JSON must be a plain object');\n }\n\n const descriptors = Object.getOwnPropertyDescriptors(value);\n const ownKeys = Reflect.ownKeys(value);\n\n if (ownKeys.length !== KEYS.length || !KEYS.every((key) => ownKeys.includes(key))) {\n throw new TypeError('Money JSON must contain only amount, currency, and unit');\n }\n\n for (const key of KEYS) {\n const descriptor = descriptors[key];\n\n if (!descriptor || !('value' in descriptor) || descriptor.get || descriptor.set) {\n throw new TypeError(`Money JSON property \"${key}\" must be a data value`);\n }\n }\n\n const amount = descriptors.amount!.value;\n const currency = descriptors.currency!.value;\n const unit = descriptors.unit!.value;\n\n if (typeof amount !== 'string' || !INTEGER.test(amount))\n throw new TypeError('Money JSON amount must be a canonical integer string');\n\n if (typeof currency !== 'string' || unit !== 'minor') throw new TypeError('Money JSON currency/unit are invalid');\n\n return { amount, currency, unit };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var Coins=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class extends t{expected;received;constructor(e,t){super(`Currency mismatch: ${e} and ${t}`),this.expected=e,this.received=t}},r=class extends t{code;constructor(e){super(`Invalid ISO 4217 currency code: "${e}"`),this.code=e}};function i(e){let t=new Map;return{get:e=>t.get(e),set(n,r){t.size>=e&&t.delete(t.keys().next().value),t.set(n,r)}}}var a=i(512);function o(e){let t=a.get(e);if(t!==void 0)return t;let n;try{n=new Intl.NumberFormat(`en`,{currency:e,style:`currency`}).resolvedOptions().maximumFractionDigits}catch{throw new r(e)}if(n===void 0)throw new r(e);return a.set(e,n),n}function s(e){return o(e),e}function c(e){return 10n**BigInt(e)}var l=/^-?\d+(\.\d+)?$/,u=/^(-?\d+\.?\d*)[eE]([+-]?\d+)$/,d=1e3;function f(e){let n=u.exec(e),r=n[1],i=parseInt(n[2],10);if(Math.abs(i)>d)throw new t(`Scientific notation exponent too large: "${e}" (max ±${d})`);let a=r.startsWith(`-`),o=a?r.slice(1):r,s=o.indexOf(`.`),c=s===-1?o:o.replace(`.`,``),l=i-(s===-1?0:o.length-s-1),f;if(l>=0)f=c+`0`.repeat(l);else{let e=c.length+l;f=e<=0?`0.`+`0`.repeat(-e)+c:c.slice(0,e)+`.`+c.slice(e)}return a?`-${f}`:f}function p(e){let n=u.test(e)?f(e):e;if(!l.test(n))throw new t(`Invalid decimal string: "${e}"`);let r=n.startsWith(`-`),i=r?n.slice(1):n,a=i.indexOf(`.`),o=a===-1?i:i.slice(0,a),s=a===-1?``:i.slice(a+1),d=c(s.length);return{denominator:d,negative:r,numerator:BigInt(o||`0`)*d+BigInt(s||`0`)}}function m(e,n,r,i,a=!1){if(n===0n)return e;switch(i){case`ceiling`:return a?e:e+1n;case`down`:return e;case`floor`:return a?e+1n:e;case`half-away-from-zero`:return n*2n>=r?e+1n:e;case`half-even`:{let t=n*2n;return t<r?e:t>r?e+1n:e%2n==0n?e:e+1n}case`up`:return e+1n;default:throw new t(`Unknown rounding mode: ${String(i)}`)}}function h(e,r,i=`half-away-from-zero`){if(e.currency!==r.from)throw new n(e.currency,r.from);if(r.rate===``)throw new t(`Exchange rate must be a non-empty decimal string`);let{denominator:a,negative:o,numerator:c}=p(String(r.rate));if(o)throw new t(`Exchange rate must be non-negative`);let l=e.amount<0n,u=(e.amount<0n?-e.amount:e.amount)*c,d=m(u/a,u%a,a,i,l);return{amount:l?-d:d,currency:s(r.to)}}var g=i(512),_=i(32);function v(e,n){let{locale:r=`en-US`,maximumFractionDigits:i,minimumFractionDigits:a,style:s=`symbol`}=n,l=o(e.currency),u=x(`maximumFractionDigits`,i,l),d=x(`minimumFractionDigits`,a,Math.min(l,u));if(d>u)throw new t(`minimumFractionDigits must be less than or equal to maximumFractionDigits`);let f=S(e.amount,l,u),p=f<0n,m=p?-f:f,h=c(u),g=m/h,_=C(u===0?``:(m%h).toString().padStart(u,`0`),d),v=w(r,e.currency,s,p);return{frac:_,intStr:T(r).format(g),negative:p,template:v}}function y(e,t={}){return b(e,t).map(e=>e.value).join(``)}function b(e,t={}){let{frac:n,intStr:r,template:i}=v(e,t),a=n.length>0,o=[],s=!1;for(let e of i)if(e.type!==`group`){if(e.type===`integer`){s||=(o.push({type:`integer`,value:r}),!0);continue}if(e.type===`decimal`){a&&o.push({type:`decimal`,value:e.value});continue}if(e.type===`fraction`){a&&o.push({type:`fraction`,value:n});continue}e.type===`currency`?o.push({type:`currency`,value:e.value}):e.type===`minusSign`?o.push({type:`minusSign`,value:e.value}):o.push({type:`literal`,value:e.value})}return o}function x(e,n,r){if(n==null)return r;if(!Number.isInteger(n)||n<0)throw new t(`${e} must be a non-negative integer`);return n}function S(e,t,n){if(t===n)return e;if(n>t)return e*c(n-t);let r=e<0n,i=r?-e:e,a=c(t-n),o=m(i/a,i%a,a,`half-away-from-zero`);return r?-o:o}function C(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 w(e,t,n,r){let i=[e,t,n,r?`neg`:`pos`].join(`\0`),a=g.get(i);if(a!==void 0)return a;let o=new Intl.NumberFormat(e,{currency:t,currencyDisplay:n,maximumFractionDigits:1,minimumFractionDigits:1,style:`currency`}).formatToParts(r?-1.1:1.1);return g.set(i,o),o}function T(e){let t=_.get(e);if(t!==void 0)return t;let n=new Intl.NumberFormat(e,{maximumFractionDigits:0,minimumFractionDigits:0,useGrouping:!0});return _.set(e,n),n}var E=!globalThis.__COINS_PROD__;function D(e){E&&console.warn(`[@vielzeug/coins] ${e}`)}function ee(e,t){let n=s(t);if(typeof e==`bigint`)return{amount:e,currency:n};if(typeof e==`number`){let t=o(n),r=String(e),i=u.test(r)?f(r):r,a=i.indexOf(`.`),s=a===-1?0:i.length-a-1;return s>t&&D(`money(): number input "${e}" has more decimal places (${s}) than ${n} supports (${t}). Use a decimal string to avoid IEEE-754 precision loss.`),{amount:k(r,t),currency:n}}return{amount:k(String(e),o(n)),currency:n}}function O(e,t){if(e.currency!==t.currency)throw new n(e.currency,t.currency)}function k(e,t){let{denominator:n,negative:r,numerator:i}=p(e),a=i*c(t),o=m(a/n,a%n,n,`half-away-from-zero`);return r?-o:o}function A(e,t){return{amount:t,currency:e.currency}}function j(e){if(typeof e!=`object`||!e)return!1;let t=e;return Object.hasOwn(t,`amount`)&&typeof t.amount==`bigint`&&Object.hasOwn(t,`currency`)&&typeof t.currency==`string`}function M(e,t){return O(e,t),{amount:e.amount+t.amount,currency:e.currency}}function N(e,t){return O(e,t),{amount:e.amount-t.amount,currency:e.currency}}function P(e,t,n=`half-away-from-zero`){let{denominator:r,negative:i,numerator:a}=p(String(t)),o=e.amount<0n!==i,s=(e.amount<0n?-e.amount:e.amount)*a,c=m(s/r,s%r,r,n,o);return{amount:o?-c:c,currency:e.currency}}function F(e,n,r=`half-away-from-zero`){let{denominator:i,negative:a,numerator:o}=p(String(n));if(o===0n)throw new t(`Division by zero`);let s=e.amount<0n!==a,c=(e.amount<0n?-e.amount:e.amount)*i,l=m(c/o,c%o,o,r,s);return{amount:s?-l:l,currency:e.currency}}function I(e,n){if(n.length===0)throw new t(`allocate requires at least one ratio`);let r=n.map(e=>p(String(e)));if(r.some(e=>e.negative))throw new t(`All ratios must be non-negative`);if(!r.some(e=>e.numerator>0n))throw new t(`At least one ratio must be positive`);let i=e.amount<0n,a=i?-e.amount:e.amount,o=r.reduce((e,t)=>t.denominator>e?t.denominator:e,1n),s=r.map(e=>e.numerator*(o/e.denominator)),c=s.reduce((e,t)=>e+t,0n),l=[],u=[];for(let e of s){let t=a*e;l.push(t/c),u.push(t%c)}let d=n.map((e,t)=>t).sort((e,t)=>{let n=u[t]-u[e];return n>0n?1:n<0n?-1:0}),f=[...l],m=a-l.reduce((e,t)=>e+t,0n);for(let e=0;m>0n;e++,m--)f[d[e]]+=1n;return f.map(t=>({amount:i?-t:t,currency:e.currency}))}function L(e){if(e.length===0)throw new t(`sum requires at least one Money value`);let r=e[0].currency;for(let t=1;t<e.length;t++)if(e[t].currency!==r)throw new n(r,e[t].currency);return{amount:e.reduce((e,t)=>e+t.amount,0n),currency:r}}function R(e){if(e.length===0)throw new t(`min requires at least one Money value`);let n=e[0];for(let t=1;t<e.length;t++)n=W(n,e[t])<=0?n:e[t];return n}function z(e){if(e.length===0)throw new t(`max requires at least one Money value`);let n=e[0];for(let t=1;t<e.length;t++)n=W(n,e[t])>=0?n:e[t];return n}function B(e,n){if(!Number.isInteger(n)||n<=0)throw new t(`splitEvenly requires a positive integer number of parts`);return I(e,Array.from({length:n},()=>1))}function V(e,r,i){if(e.currency!==r.currency||e.currency!==i.currency)throw new n(e.currency,e.currency===r.currency?i.currency:r.currency);if(W(r,i)>0)throw new t(`clamp: lower (${$(r)} ${r.currency}) must be <= upper (${$(i)} ${i.currency})`);return z([r,R([e,i])])}function H(e){return{amount:e.amount<0n?-e.amount:e.amount,currency:e.currency}}function U(e){return{amount:-e.amount,currency:e.currency}}function W(e,t){return O(e,t),e.amount<t.amount?-1:+(e.amount>t.amount)}function G(e,t){return e.currency===t.currency&&e.amount===t.amount}function K(e,t){return W(e,t)>0}function q(e,t){return W(e,t)>=0}function J(e,t){return W(e,t)<0}function Y(e,t){return W(e,t)<=0}function X(e){return e.amount===0n}function Z(e){return e.amount>0n}function Q(e){return e.amount<0n}function te(e){return e.amount>=0n}function ne(e){return e.amount<=0n}function re(e){return{amount:String(e.amount),currency:e.currency}}function ie(e){let n=s(e.currency);if(typeof e.amount!=`string`)throw new t(`Invalid money amount in JSON: ${String(e.amount)} (expected an integer string, e.g. '123456')`);let r;try{r=BigInt(e.amount)}catch{throw new t(`Invalid money amount in JSON: "${e.amount}" (expected an integer string, e.g. '123456')`)}return{amount:r,currency:n}}function $(e){let t=o(e.currency);if(t===0)return String(e.amount);let n=e.amount<0n,r=n?-e.amount:e.amount,i=c(t),a=r/i,s=(r%i).toString().padStart(t,`0`);return`${n?`-`:``}${a}.${s}`}function ae(e){let t=o(e.currency);return Number(e.amount)/Number(c(t))}function oe(e,n,r=`half-away-from-zero`){if(!Number.isInteger(n)||n<0)throw new t(`roundTo: places must be a non-negative integer, got ${n}`);let i=o(e.currency);if(n>i)throw new t(`roundTo: places (${n}) exceeds the decimal places for ${e.currency} (${i})`);if(n===i)return e;let a=c(i-n),s=e.amount<0n,l=s?-e.amount:e.amount,u=m(l/a,l%a,a,r,s);return{amount:s?-u:u,currency:e.currency}}return e.CoinsError=t,e.CurrencyMismatchError=n,e.InvalidCurrencyError=r,e.abs=H,e.add=M,e.allocate=I,e.clamp=V,e.compare=W,e.divide=F,e.exchange=h,e.format=y,e.formatParts=b,e.fromJSON=ie,e.getCurrencyDecimals=o,e.greaterThan=K,e.greaterThanOrEqual=q,e.isEqual=G,e.isMoney=j,e.isNegative=Q,e.isNonNegative=te,e.isNonPositive=ne,e.isPositive=Z,e.isZero=X,e.lessThan=J,e.lessThanOrEqual=Y,e.max=z,e.min=R,e.money=ee,e.multiply=P,e.negate=U,e.roundTo=oe,e.splitEvenly=B,e.subtract=N,e.sum=L,e.toDecimal=$,e.toJSON=re,e.toNumber=ae,e.validateCurrencyCode=s,e.withAmount=A,e})({});
1
+ var Coins=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{code;static is(t){return t instanceof e}constructor(e,t,n){super(t,n),this.code=e,this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},n=class extends t{expected;received;constructor(e,t){super(`CURRENCY_MISMATCH`,`Currency mismatch: ${e} and ${t}`),this.expected=e,this.received=t}},r=class extends t{value;constructor(e){super(`INVALID_CURRENCY`,`Unsupported currency: "${String(e)}"`),this.value=e}},i=/^(-?)(\d+)(?:\.(\d+))?$/,a=1e3,o=100;function s(e){if(e.length>a)throw new t(`INVALID_DECIMAL`,`Decimal input is too long`);let n=i.exec(e);if(!n)throw new t(`INVALID_DECIMAL`,`Invalid decimal: "${e}"`);let r=(n[3]??``).replace(/0+$/,``);if(r.length>o)throw new t(`INVALID_DECIMAL`,`Decimal precision cannot exceed ${o} places`);let s=10n**BigInt(r.length),c=BigInt(n[2])*s+BigInt(r||`0`),l=n[1]===`-`&&c!==0n?-c:c,d=u(l<0n?-l:l,s);return Object.freeze({denominator:s/d,numerator:l/d})}function c(e,n,r){if(n<=0n)throw new t(`INVALID_DECIMAL`,`Decimal denominator must be positive`);let i=e<0n,a=i?-e:e,o=a/n,s=a%n;if(s===0n)return i?-o:o;let c=(()=>{switch(r){case`awayFromZero`:return!0;case`ceil`:return!i;case`floor`:return i;case`halfAwayFromZero`:return s*2n>=n;case`halfEven`:{let e=s*2n;return e>n||e===n&&o%2n!=0n}case`towardZero`:return!1}})()?o+1n:o;return i?-c:c}function l(e,t){let n=e<0n,r=n?-e:e,i=10n**BigInt(t),a=r/i;if(t===0)return`${n?`-`:``}${a}`;let o=(r%i).toString().padStart(t,`0`);return`${n?`-`:``}${a}.${o}`}function u(e,t){let n=e,r=t;for(;r!==0n;)[n,r]=[r,n%r];return n===0n?1n:n}function d(e,t){return e/u(e,t)*t}var f=new Map,p=new Map;function m(e,t){let n=T(e,t);return f.set(e,n),n}var h=m(`USD`,2),g=m(`EUR`,2),_=m(`GBP`,2),v=m(`JPY`,0),y=m(`KRW`,0),b=m(`BHD`,3),x=m(`KWD`,3);function S({code:e,minorUnit:t}){E(e,t);let n=f.get(e);if(n){if(n.minorUnit!==t)throw D(e,n.minorUnit);return n}let r=p.get(e);if(r){if(r.minorUnit!==t)throw D(e,r.minorUnit);return r}let i=T(e,t);return p.set(e,i),i}function C(e){let t=f.get(e)??p.get(e);if(!t)throw new r(e);return t}function w(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.code==`string`&&(f.get(t.code)===e||p.get(t.code)===e)}function ee(e){let t=f.get(e);if(!t)throw new r(e);return t}function T(e,t){return E(e,t),Object.freeze({code:e,minorUnit:t})}function E(e,n){if(!/^[A-Z]{3}$/.test(e))throw new t(`INVALID_CURRENCY`,`Currency code must be three uppercase letters: "${e}"`);if(!Number.isInteger(n)||n<0||n>6)throw new t(`INVALID_CURRENCY`,`Currency "${e}" must have 0–6 minor-unit digits`)}function D(e,n){return new t(`INVALID_CURRENCY`,`Currency "${e}" already has ${n} minor-unit digits`)}var O=`halfAwayFromZero`;function k(e,n,r){if(V(n),typeof e==`bigint`){if(!r||!(`unit`in r)||r.unit!==`minor`)throw new t(`INVALID_MONEY`,`Bigint amounts require { unit: 'minor' }`);return R(e,n)}if(r&&`unit`in r)throw new t(`INVALID_MONEY`,`Decimal strings do not accept a unit option`);let i=s(e),a=i.numerator*10n**BigInt(n.minorUnit);if(a%i.denominator!==0n&&!r)throw new t(`INVALID_MONEY`,`Amount "${e}" exceeds ${n.code} precision; provide rounding`);return R(c(a,i.denominator,r?.rounding??O),n)}function A(e){if(!H(e))throw new t(`INVALID_MONEY`,`Money must be a plain data object`);let n=Object.getOwnPropertyDescriptors(e),r=n.amount,i=n.currency;if(!U(r)||!U(i))throw new t(`INVALID_MONEY`,`Money properties must be plain data values`);if(typeof r.value!=`bigint`||!w(i.value))throw new t(`INVALID_MONEY`,`Money must contain bigint amount and a registered currency`);return R(r.value,i.value)}function j(e){try{return A(e),!0}catch{return!1}}function te(e,t){return B(e,t),R(e.amount+t.amount,e.currency)}function ne(e,t){return B(e,t),R(e.amount-t.amount,e.currency)}function re(e,t,n={}){z(e);let r=s(t);return R(c(e.amount*r.numerator,r.denominator,n.rounding??O),e.currency)}function ie(e,n,r={}){z(e);let i=s(n);if(i.numerator===0n)throw new t(`DIVISION_BY_ZERO`,`Cannot divide money by zero`);return R(c(e.amount*i.denominator,i.numerator<0n?-i.numerator:i.numerator,r.rounding??O)*(i.numerator<0n?-1n:1n),e.currency)}function M(e,t){return B(e,t),e.amount===t.amount?0:e.amount<t.amount?-1:1}function N(e){return z(e),R(e.amount<0n?-e.amount:e.amount,e.currency)}function P(e){return z(e),R(-e.amount,e.currency)}function F(e,n){z(e);let{fractionDigits:r,rounding:i=O}=n;if(!Number.isInteger(r)||r<0||r>e.currency.minorUnit)throw new t(`INVALID_ROUNDING`,`fractionDigits must be an integer from 0 to ${e.currency.minorUnit}`);let a=10n**BigInt(e.currency.minorUnit-r);return R(c(e.amount,a,i)*a,e.currency)}function I(e){return z(e),l(e.amount,e.currency.minorUnit)}function L(e,t){return V(t),R(e,t)}function R(e,t){return Object.freeze({amount:e,currency:t})}function z(e){if(!Object.isFrozen(e)||!w(e.currency))throw new t(`INVALID_MONEY`,`Money must be a canonical Coins value`)}function B(e,t){if(z(e),z(t),e.currency!==t.currency)throw new n(e.currency.code,t.currency.code)}function V(e){if(!w(e))throw new t(`INVALID_CURRENCY`,`Money requires a registered currency`)}function H(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function U(e){return e!==void 0&&`value`in e&&e.get===void 0&&e.set===void 0}function W(e,r){let i=0n;for(let a of e){if(!j(a))throw new t(`INVALID_MONEY`,`sum() requires canonical money values`);if(a.currency!==r.currency)throw new n(r.currency.code,a.currency.code);i+=a.amount}return L(i,r.currency)}function G(e,n){if(!j(e))throw new t(`INVALID_MONEY`,`allocate() requires canonical money`);if(typeof n==`number`)return q(e,n);if(n.length===0)throw new t(`INVALID_ALLOCATION`,`Allocation weights cannot be empty`);let r=n.map(s);if(r.some(e=>e.numerator<0n))throw new t(`INVALID_ALLOCATION`,`Allocation weights cannot be negative`);let i=r.reduce((e,t)=>d(e,t.denominator),1n),a=r.map(e=>e.numerator*(i/e.denominator)),o=a.reduce((e,t)=>e+t,0n);if(o===0n)throw new t(`INVALID_ALLOCATION`,`Allocation weights must include a positive value`);let c=e.amount<0n?-1n:1n,l=e.amount<0n?-e.amount:e.amount,u=a.map(e=>l*e/o),f=l-u.reduce((e,t)=>e+t,0n),p=a.map((e,t)=>({index:t,remainder:l*e%o})).sort((e,t)=>e.remainder===t.remainder?e.index-t.index:e.remainder>t.remainder?-1:1);for(let e of p){if(f===0n)break;u[e.index]+=1n,f-=1n}return u.map(t=>L(t*c,e.currency))}function K(e,n){if(M(n.min,n.max)===1)throw new t(`INVALID_ALLOCATION`,`Clamp minimum cannot exceed maximum`);return M(e,n.min)===-1?n.min:M(e,n.max)===1?n.max:e}function q(e,n){if(!Number.isInteger(n)||n<1)throw new t(`INVALID_ALLOCATION`,`Allocation count must be a positive integer`);let r=BigInt(n),i=e.amount<0n?-1n:1n,a=e.amount<0n?-e.amount:e.amount,o=a/r,s=a%r;return Array.from({length:n},(t,n)=>L((o+(BigInt(n)<s?1n:0n))*i,e.currency))}function J({from:e,to:n,value:r}){if(!w(e)||!w(n))throw new t(`INVALID_CURRENCY`,`Exchange rate requires registered currencies`);let i=s(r);if(i.numerator<0n)throw new t(`INVALID_DECIMAL`,`Exchange rates cannot be negative`);return Object.freeze({from:e,to:n,value:i})}function ae(e,r,i={}){if(!j(e)||!oe(r))throw new t(`INVALID_MONEY`,`Exchange requires canonical money and rate values`);if(e.currency!==r.from)throw new n(e.currency.code,r.from.code);return L(c(e.amount*r.value.numerator*10n**BigInt(r.to.minorUnit),r.value.denominator*10n**BigInt(r.from.minorUnit),i.rounding??`halfAwayFromZero`),r.to)}function oe(e){if(typeof e!=`object`||!e||!Object.isFrozen(e))return!1;let t=e;return w(t.from)&&w(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}var Y=20,X=new Map,Z=new Map;function se(e,t={}){return Q(e,t).map(e=>e.value).join(``)}function Q(e,n={}){if(!j(e))throw new t(`INVALID_MONEY`,`format() requires canonical money`);let{locale:r=`en-US`,maximumFractionDigits:i=e.currency.minorUnit,minimumFractionDigits:a=e.currency.minorUnit,style:o=`symbol`}=n;ce(a,i);let s=10n**BigInt(i),l=i>=e.currency.minorUnit?e.amount*10n**BigInt(i-e.currency.minorUnit):c(e.amount,10n**BigInt(e.currency.minorUnit-i),`halfAwayFromZero`),u=l<0n,d=u?-l:l,f=d/s,p=(i===0?``:(d%s).toString().padStart(i,`0`)).replace(RegExp(`0{0,${Math.max(0,i-a)}}$`),``),m=le(r,e.currency.code,o,u),h=[],g=!1;for(let e of m)e.type!==`group`&&(e.type===`integer`?g||=(h.push({type:`integer`,value:ue(r).format(f)}),!0):e.type===`decimal`?p&&h.push({type:`decimal`,value:e.value}):e.type===`fraction`?p&&h.push({type:`fraction`,value:p}):e.type===`currency`||e.type===`minusSign`||e.type===`plusSign`?h.push({type:e.type,value:e.value}):h.push({type:`literal`,value:e.value}));return h}function ce(e,n){if(!Number.isInteger(n)||!Number.isInteger(e)||e<0||n<e||n>Y)throw new t(`INVALID_ROUNDING`,`Fraction digits must be integers satisfying 0 ≤ minimum ≤ maximum ≤ ${Y}`)}function le(e,n,r,i){let a=`${e}\0${n}\0${r}\0${i}`,o=Z.get(a);if(o)return o;try{let t=new Intl.NumberFormat(e,{currency:n,currencyDisplay:r,maximumFractionDigits:1,minimumFractionDigits:1,style:`currency`}).formatToParts(i?-1.1:1.1);return Z.set(a,t),t}catch(r){throw new t(`INVALID_CURRENCY`,`Cannot format currency "${n}" for locale "${e}"`,{cause:r})}}function ue(e){let n=X.get(e);if(n)return n;try{let t=new Intl.NumberFormat(e,{maximumFractionDigits:0,useGrouping:!0});return X.set(e,t),t}catch(n){throw new t(`INVALID_ROUNDING`,`Cannot create number formatter for locale "${e}"`,{cause:n})}}var de=/^(?:0|-[1-9]\d*|[1-9]\d*)$/,$=[`amount`,`currency`,`unit`];function fe(e){return{amount:e.amount.toString(),currency:e.currency.code,unit:`minor`}}function pe(e,n={}){try{let t=me(e),r=n.currency??ee;return k(BigInt(t.amount),r(t.currency),{unit:`minor`})}catch(e){throw new t(`INVALID_MONEY`,`Invalid Money JSON`,{cause:e})}}function me(e){if(typeof e!=`object`||!e||Object.getPrototypeOf(e)!==Object.prototype)throw TypeError(`Money JSON must be a plain object`);let t=Object.getOwnPropertyDescriptors(e),n=Reflect.ownKeys(e);if(n.length!==$.length||!$.every(e=>n.includes(e)))throw TypeError(`Money JSON must contain only amount, currency, and unit`);for(let e of $){let n=t[e];if(!n||!(`value`in n)||n.get||n.set)throw TypeError(`Money JSON property "${e}" must be a data value`)}let r=t.amount.value,i=t.currency.value,a=t.unit.value;if(typeof r!=`string`||!de.test(r))throw TypeError(`Money JSON amount must be a canonical integer string`);if(typeof i!=`string`||a!==`minor`)throw TypeError(`Money JSON currency/unit are invalid`);return{amount:r,currency:i,unit:a}}return e.BHD=b,e.CoinsError=t,e.CurrencyMismatchError=n,e.EUR=g,e.GBP=_,e.InvalidCurrencyError=r,e.JPY=v,e.KRW=y,e.KWD=x,e.USD=h,e.abs=N,e.add=te,e.allocate=G,e.clamp=K,e.compare=M,e.currency=C,e.decimal=s,e.defineCurrency=S,e.divide=ie,e.exchange=ae,e.exchangeRate=J,e.format=se,e.formatParts=Q,e.isCurrency=w,e.isMoney=j,e.money=k,e.multiply=re,e.negate=P,e.parseMoney=A,e.parseMoneyJSON=pe,e.round=F,e.subtract=ne,e.sum=W,e.toDecimal=I,e.toJSON=fe,e})({});
2
2
  //# sourceMappingURL=coins.iife.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"coins.iife.js","names":[],"sources":["../src/errors.ts","../src/_cache.ts","../src/utils.ts","../src/exchange.ts","../src/format.ts","../src/_dev.ts","../src/money.ts"],"sourcesContent":["/** Base class for all coins errors. Use `instanceof CoinsError` to catch any coins-originated error. */\nexport class CoinsError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is CoinsError {\n return err instanceof CoinsError;\n }\n}\n\n/**\n * Thrown when two `Money` values with different currencies are used in an\n * operation that requires the same currency (e.g. `add`, `compare`, `exchange`).\n *\n * @example\n * ```ts\n * import { CurrencyMismatchError } from '@vielzeug/coins';\n *\n * try {\n * add(usd, eur);\n * } catch (e) {\n * if (e instanceof CurrencyMismatchError) {\n * console.log(e.expected, e.received);\n * }\n * }\n * ```\n */\nexport class CurrencyMismatchError extends CoinsError {\n /** The currency of the first operand. */\n readonly expected: string;\n /** The currency of the second (mismatching) operand. */\n readonly received: string;\n\n constructor(expected: string, received: string) {\n super(`Currency mismatch: ${expected} and ${received}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\n/**\n * Thrown when an unrecognised ISO 4217 currency code is passed to `money`,\n * `exchange`, or any function that validates a currency string.\n *\n * @example\n * ```ts\n * import { InvalidCurrencyError } from '@vielzeug/coins';\n *\n * try {\n * money('1.00', 'FAKE');\n * } catch (e) {\n * if (e instanceof InvalidCurrencyError) {\n * console.log('Bad code:', e.code);\n * }\n * }\n * ```\n */\nexport class InvalidCurrencyError extends CoinsError {\n /** The unrecognised currency code that was provided. */\n readonly code: string;\n\n constructor(code: string) {\n super(`Invalid ISO 4217 currency code: \"${code}\"`);\n this.code = code;\n }\n}\n","/** A FIFO bounded Map cache that evicts the oldest entry when full. */\nexport function boundedCache<K, V>(maxSize: number): { get(k: K): V | undefined; set(k: K, v: V): void } {\n const map = new Map<K, V>();\n\n return {\n get: (k) => map.get(k),\n set(k, v) {\n if (map.size >= maxSize) map.delete(map.keys().next().value as K);\n\n map.set(k, v);\n },\n };\n}\n","import type { CurrencyCode, RoundingMode } from './types';\n\nimport { boundedCache } from './_cache';\nimport { CoinsError, InvalidCurrencyError } from './errors';\n\nconst currencyDecimalsCache = boundedCache<string, number>(512);\n\n/**\n * Returns the number of minor-unit decimal places for a given ISO 4217 currency code.\n * Uses `Intl.NumberFormat` to resolve the canonical value (e.g. USD→2, JPY→0, KWD→3).\n * Throws `InvalidCurrencyError` for unrecognized codes or when the runtime cannot determine decimals.\n */\nexport function getCurrencyDecimals(currencyCode: CurrencyCode): number {\n const cached = currencyDecimalsCache.get(currencyCode);\n\n if (cached !== undefined) return cached;\n\n let resolved: number | undefined;\n\n try {\n resolved = new Intl.NumberFormat('en', { currency: currencyCode, style: 'currency' }).resolvedOptions()\n .maximumFractionDigits;\n } catch {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n if (resolved === undefined) {\n throw new InvalidCurrencyError(currencyCode);\n }\n\n currencyDecimalsCache.set(currencyCode, resolved);\n\n return resolved;\n}\n\n/**\n * Validates an ISO 4217 currency code and returns it.\n * Throws `InvalidCurrencyError` if unrecognized. Uses `getCurrencyDecimals` internally so\n * the result is cached for subsequent decimal lookups.\n */\nexport function validateCurrencyCode(code: CurrencyCode): CurrencyCode {\n getCurrencyDecimals(code);\n\n return code;\n}\n\n/** Returns `10n ** BigInt(exponent)`. */\nexport function pow10(exponent: number): bigint {\n return 10n ** BigInt(exponent);\n}\n\n/** Regex for valid decimal strings accepted by `parseRational`. */\nconst DECIMAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/** Regex matching JavaScript scientific-notation strings, e.g. `'1e-7'`, `'-3.14E+5'`. */\nexport const SCIENTIFIC_RE = /^(-?\\d+\\.?\\d*)[eE]([+-]?\\d+)$/;\n\n/** Maximum absolute exponent allowed in scientific notation. Prevents '0'.repeat(N) allocation attacks. */\nconst MAX_SCIENTIFIC_EXP = 1000;\n\n/**\n * Expands a scientific-notation decimal string into a plain decimal string.\n * `'1e-7'` → `'0.0000001'`, `'1.23e+5'` → `'123000'`.\n * Only called when `SCIENTIFIC_RE` already matched — no extra validation.\n * @throws {CoinsError} If the absolute exponent exceeds `MAX_SCIENTIFIC_EXP` (1000).\n */\nexport function expandScientific(s: string): string {\n const match = SCIENTIFIC_RE.exec(s)!;\n const coeff = match[1]!;\n const exp = parseInt(match[2]!, 10);\n\n if (Math.abs(exp) > MAX_SCIENTIFIC_EXP) {\n throw new CoinsError(`Scientific notation exponent too large: \"${s}\" (max ±${MAX_SCIENTIFIC_EXP})`);\n }\n\n const isNeg = coeff.startsWith('-');\n const absCoeff = isNeg ? coeff.slice(1) : coeff;\n const dotIndex = absCoeff.indexOf('.');\n const digits = dotIndex === -1 ? absCoeff : absCoeff.replace('.', '');\n const fracLen = dotIndex === -1 ? 0 : absCoeff.length - dotIndex - 1;\n const newExp = exp - fracLen; // where the decimal point sits relative to end of digits\n\n let result: string;\n\n if (newExp >= 0) {\n // All digits are to the left of the decimal — append trailing zeros\n result = digits + '0'.repeat(newExp);\n } else {\n const intLen = digits.length + newExp; // digits before the decimal point\n\n if (intLen <= 0) {\n // Pure fraction: '0.' + leading zeros + digits\n result = '0.' + '0'.repeat(-intLen) + digits;\n } else {\n result = digits.slice(0, intLen) + '.' + digits.slice(intLen);\n }\n }\n\n return isNeg ? `-${result}` : result;\n}\n\n/**\n * Parses a decimal string into a rational `{ numerator, denominator, negative }`.\n * The denominator is always a power of 10 and the numerator is always non-negative.\n * @throws {CoinsError} For non-numeric input.\n *\n * Accepts standard decimal strings and JavaScript scientific notation\n * (e.g. `'1e-7'`, `'1.23E+5'`) — the latter is automatically expanded before parsing.\n *\n * @example\n * parseRational('1.5') → { numerator: 15n, denominator: 10n, negative: false }\n * parseRational('-0.5') → { numerator: 5n, denominator: 10n, negative: true }\n * parseRational('3') → { numerator: 3n, denominator: 1n, negative: false }\n * parseRational('1e-7') → { numerator: 1n, denominator: 10000000n, negative: false }\n *\n * Note: `'-0'` and `'-0.0'` produce `{ negative: true, numerator: 0n, ... }`.\n * Callers that compute `negative ? -result : result` on `0n` remain correct\n * because `-0n === 0n` in bigint arithmetic.\n */\nexport function parseRational(str: string): { denominator: bigint; negative: boolean; numerator: bigint } {\n // Expand scientific notation (e.g. from String(1e-7) = '1e-7') before validation.\n const normalized = SCIENTIFIC_RE.test(str) ? expandScientific(str) : str;\n\n if (!DECIMAL_RE.test(normalized)) {\n throw new CoinsError(`Invalid decimal string: \"${str}\"`);\n }\n\n const negative = normalized.startsWith('-');\n const absStr = negative ? normalized.slice(1) : normalized;\n const dotIndex = absStr.indexOf('.');\n const intStr = dotIndex === -1 ? absStr : absStr.slice(0, dotIndex);\n const fracStr = dotIndex === -1 ? '' : absStr.slice(dotIndex + 1);\n const denominator = pow10(fracStr.length);\n const numerator = BigInt(intStr || '0') * denominator + BigInt(fracStr || '0');\n\n return { denominator, negative, numerator };\n}\n\n/**\n * Applies a rounding mode to a truncated division result.\n *\n * **Contract**: `quotient` and `absRemainder` must be non-negative (absolute values).\n * The caller is responsible for:\n * 1. Computing everything in terms of absolute values.\n * 2. Passing `negative = true` when the true mathematical result is negative.\n * 3. Applying the sign after: `negative ? -result : result`.\n *\n * Returns the absolute value of the rounded result.\n *\n * @param quotient Non-negative integer part of the division (`|dividend| / divisor`).\n * @param absRemainder Non-negative remainder (`|dividend| % divisor`).\n * @param denominator The divisor used to produce quotient/remainder.\n * @param mode The desired rounding mode.\n * @param negative Whether the true mathematical result is negative. Defaults to `false`.\n */\nexport function applyRounding(\n quotient: bigint,\n absRemainder: bigint,\n denominator: bigint,\n mode: RoundingMode,\n negative = false,\n): bigint {\n if (absRemainder === 0n) return quotient;\n\n switch (mode) {\n case 'ceiling':\n // Toward +∞: round up for positive results, truncate for negative results.\n return negative ? quotient : quotient + 1n;\n\n case 'down':\n // Toward zero: always truncate.\n return quotient;\n\n case 'floor':\n // Toward −∞: truncate for positive results, one step more for negative results.\n return negative ? quotient + 1n : quotient;\n\n case 'half-away-from-zero':\n return absRemainder * 2n >= denominator ? quotient + 1n : quotient;\n\n case 'half-even': {\n const twice = absRemainder * 2n;\n\n if (twice < denominator) return quotient;\n\n if (twice > denominator) return quotient + 1n;\n\n // Exactly half — round to the nearest even integer.\n return quotient % 2n === 0n ? quotient : quotient + 1n;\n }\n\n case 'up':\n // Away from zero: always round away.\n return quotient + 1n;\n\n default:\n throw new CoinsError(`Unknown rounding mode: ${String(mode)}`);\n }\n}\n","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","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","const isDev = !(globalThis as { __COINS_PROD__?: boolean }).__COINS_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/coins] ${msg}`);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n","import type { CurrencyCode, Money, MoneyJSON, RoundingMode } from './types';\n\nimport { warn } from './_dev';\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport {\n applyRounding,\n expandScientific,\n getCurrencyDecimals,\n parseRational,\n pow10,\n SCIENTIFIC_RE,\n validateCurrencyCode,\n} from './utils';\n\nexport { CoinsError, CurrencyMismatchError, InvalidCurrencyError } from './errors';\n\n// ─── Factories ───────────────────────────────────────────────────────────────\n\n/**\n * Creates a `Money` value from a decimal string, number, or raw bigint minor units.\n * Validates the currency code — throws `InvalidCurrencyError` for unrecognised ISO 4217 codes.\n *\n * - **string** `'1234.56'` → parsed losslessly; respects the currency's decimal places.\n * - **number** `1234.56` → converted via `String()` first; inherits IEEE-754 limits.\n * Prefer strings when precision matters.\n * - **bigint** `123456n` → used as-is (already in minor units).\n *\n * @example\n * ```ts\n * money('1234.56', 'USD') // { amount: 123456n, currency: 'USD' }\n * money(1234, 'JPY') // { amount: 1234n, currency: 'JPY' }\n * money(123456n, 'USD') // { amount: 123456n, currency: 'USD' }\n * ```\n */\nexport function money(amount: bigint | number | string, currency: CurrencyCode): Money {\n const validCurrency = validateCurrencyCode(currency);\n\n if (typeof amount === 'bigint') {\n return { amount, currency: validCurrency };\n }\n\n if (typeof amount === 'number') {\n const decimals = getCurrencyDecimals(validCurrency);\n const str = String(amount);\n // Expand scientific notation (e.g. String(1e-7) === '1e-7') before measuring fraction\n // length — otherwise inputs like 1e-7 report 0 fractional digits and skip the warning\n // below despite having far more precision than the currency supports.\n const normalized = SCIENTIFIC_RE.test(str) ? expandScientific(str) : str;\n const dotIndex = normalized.indexOf('.');\n const fracLen = dotIndex === -1 ? 0 : normalized.length - dotIndex - 1;\n\n if (fracLen > decimals) {\n warn(\n `money(): number input \"${amount}\" has more decimal places (${fracLen}) than ${validCurrency} supports (${decimals}). ` +\n `Use a decimal string to avoid IEEE-754 precision loss.`,\n );\n }\n\n // getCurrencyDecimals is cached from the validateCurrencyCode call above.\n return { amount: parseToMinorUnits(str, decimals), currency: validCurrency };\n }\n\n // getCurrencyDecimals is cached from the validateCurrencyCode call above.\n return { amount: parseToMinorUnits(String(amount), getCurrencyDecimals(validCurrency)), currency: validCurrency };\n}\n\n// ─── Internal ─────────────────────────────────────────────────────────────────\n\nfunction assertSameCurrency(a: Money, b: Money): void {\n if (a.currency !== b.currency) {\n throw new CurrencyMismatchError(a.currency, b.currency);\n }\n}\n\n/**\n * Converts a decimal string to minor units for the given decimal precision.\n * Uses parseRational + applyRounding to share the single rounding implementation.\n */\nfunction parseToMinorUnits(str: string, decimals: number): bigint {\n const { denominator, negative, numerator } = parseRational(str);\n // numerator/denominator × 10^decimals = numerator × 10^decimals / denominator\n const scale = pow10(decimals);\n const raw = numerator * scale;\n const quotient = raw / denominator;\n const result = applyRounding(quotient, raw % denominator, denominator, 'half-away-from-zero');\n\n return negative ? -result : result;\n}\n\n/**\n * Creates a new `Money` value with the given `amount` and the same currency as `m`.\n * Useful when you compute a raw `bigint` amount externally and need to wrap it\n * back into a `Money` value without re-validating the currency.\n *\n * @example\n * ```ts\n * const price = money('9.99', 'USD');\n * withAmount(price, 1999n) // { amount: 1999n, currency: 'USD' }\n * ```\n */\nexport function withAmount(m: Money, amount: bigint): Money {\n return { amount, currency: m.currency };\n}\n\n/**\n * Type guard that returns `true` if `value` is a `Money`-shaped object\n * (has a `bigint` `amount` and a `string` `currency`).\n * Useful for narrowing unknown payloads from APIs or deserialized storage.\n *\n * Note: does **not** validate the currency code — use `money(0n, currency)` or\n * `validateCurrencyCode()` if you also need to confirm it is a recognized ISO 4217 code.\n *\n * @example\n * ```ts\n * isMoney({ amount: 100n, currency: 'USD' }) // true\n * isMoney({ amount: 1.5, currency: 'USD' }) // false\n * isMoney(null) // false\n * ```\n */\nexport function isMoney(value: unknown): value is Money {\n if (typeof value !== 'object' || value === null) return false;\n\n const v = value as Record<string, unknown>;\n\n return (\n Object.hasOwn(v, 'amount') &&\n typeof v.amount === 'bigint' &&\n Object.hasOwn(v, 'currency') &&\n typeof v.currency === 'string'\n );\n}\n\n// ─── Arithmetic ──────────────────────────────────────────────────────────────\n\n/** Adds two `Money` values. Throws if their currencies differ. */\nexport function add(a: Money, b: Money): Money {\n assertSameCurrency(a, b);\n\n return { amount: a.amount + b.amount, currency: a.currency };\n}\n\n/** Subtracts `b` from `a`. Throws if their currencies differ. */\nexport function subtract(a: Money, b: Money): Money {\n assertSameCurrency(a, b);\n\n return { amount: a.amount - b.amount, currency: a.currency };\n}\n\n/**\n * Multiplies a `Money` value by a scalar factor.\n * The factor can be a decimal string (lossless) or a number.\n *\n * @param mode Rounding mode for fractional minor units. Defaults to `'half-away-from-zero'`.\n *\n * @example\n * ```ts\n * multiply(money('100.00', 'USD'), '1.5') // $150.00\n * multiply(money('1.00', 'USD'), '0.339', 'floor') // $0.33\n * multiply(money('1.00', 'USD'), '0.339', 'ceiling') // $0.34\n * ```\n */\nexport function multiply(m: Money, factor: number | string, mode: RoundingMode = 'half-away-from-zero'): Money {\n const { denominator, negative: factorNegative, numerator } = parseRational(String(factor));\n const negative = m.amount < 0n !== factorNegative;\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: m.currency };\n}\n\n/**\n * Divides a `Money` value by a scalar divisor.\n * The divisor can be a decimal string (lossless) or a number.\n *\n * @param mode Rounding mode for fractional minor units. Defaults to `'half-away-from-zero'`.\n * @throws {CoinsError} On division by zero.\n *\n * @example\n * ```ts\n * divide(money('100.00', 'USD'), 3) // $33.33\n * divide(money('100.00', 'USD'), 3, 'ceiling') // $33.34\n * ```\n */\nexport function divide(m: Money, divisor: number | string, mode: RoundingMode = 'half-away-from-zero'): Money {\n const { denominator: parsedDenom, negative: divisorNegative, numerator } = parseRational(String(divisor));\n\n if (numerator === 0n) throw new CoinsError('Division by zero');\n\n const negative = m.amount < 0n !== divisorNegative;\n const absAmount = m.amount < 0n ? -m.amount : m.amount;\n // Dividing by (numerator/parsedDenom) = multiplying by (parsedDenom/numerator).\n const raw = absAmount * parsedDenom;\n const quotient = raw / numerator;\n const result = applyRounding(quotient, raw % numerator, numerator, mode, negative);\n\n return { amount: negative ? -result : result, currency: m.currency };\n}\n\n/**\n * Distributes `money` across `ratios` without losing or gaining a single minor unit.\n * Accepts both number and string ratios — use strings for lossless decimal weights.\n *\n * Uses the Largest Remainder Method: each share gets its floor allocation first,\n * then any remainder units are assigned one-by-one to the shares with the largest\n * fractional parts, breaking ties by original index (stable, left-to-right).\n *\n * Throws if `ratios` is empty, contains negative values (including negative strings like `'-0.5'`), or sums to zero.\n *\n * @example\n * ```ts\n * allocate(money('10.00', 'USD'), [1, 1, 1])\n * // → [$3.34, $3.33, $3.33] (not three × $3.33 — that loses a penny)\n *\n * allocate(money('10.00', 'USD'), ['0.3', '0.7'])\n * // → [$3.00, $7.00]\n * ```\n */\nexport function allocate(m: Money, ratios: readonly (number | string)[]): [Money, ...Money[]] {\n if (ratios.length === 0) throw new CoinsError('allocate requires at least one ratio');\n\n const parsedRatios = ratios.map((r) => parseRational(String(r)));\n\n if (parsedRatios.some((p) => p.negative)) throw new CoinsError('All ratios must be non-negative');\n\n if (!parsedRatios.some((p) => p.numerator > 0n)) throw new CoinsError('At least one ratio must be positive');\n\n const negative = m.amount < 0n;\n const absAmount = negative ? -m.amount : m.amount;\n\n // All denominators are powers of 10; the max is their LCM — use it to normalise weights to integers.\n const maxDenominator = parsedRatios.reduce((max, p) => (p.denominator > max ? p.denominator : max), 1n);\n const bigWeights = parsedRatios.map((p) => p.numerator * (maxDenominator / p.denominator));\n const bigTotal = bigWeights.reduce((a, b) => a + b, 0n);\n\n // Floor allocation + fractional remainders.\n const floors: bigint[] = [];\n const fracRemainders: bigint[] = [];\n\n for (const w of bigWeights) {\n const ideal = absAmount * w;\n\n floors.push(ideal / bigTotal);\n fracRemainders.push(ideal % bigTotal);\n }\n\n // Distribute leftover units to the shares with the largest fractional remainders.\n // Relies on V8/SpiderMonkey's stable sort (ECMAScript 2019+ spec-mandated): ties\n // in fractional remainders are broken by original index (left-to-right), ensuring\n // deterministic output for equal-weight ratios (e.g. [1, 1, 1]).\n const indices = ratios\n .map((_, i) => i)\n .sort((a, b) => {\n const diff = fracRemainders[b]! - fracRemainders[a]!;\n\n return diff > 0n ? 1 : diff < 0n ? -1 : 0;\n });\n\n const result = [...floors];\n // `remaining` is always < ratios.length (at most one unit per share), safe for this loop.\n let remaining = absAmount - floors.reduce((a, b) => a + b, 0n);\n\n for (let i = 0; remaining > 0n; i++, remaining--) {\n result[indices[i]!]! += 1n;\n }\n\n return result.map((amount) => ({ amount: negative ? -amount : amount, currency: m.currency })) as [Money, ...Money[]];\n}\n\n// ─── Aggregates ──────────────────────────────────────────────────────────────\n\n/**\n * Sums an array of `Money` values. Throws if the array is empty or currencies differ.\n *\n * @example\n * ```ts\n * sum([money('1.00', 'USD'), money('2.50', 'USD')]) // $3.50\n * ```\n */\nexport function sum(moneys: readonly Money[]): Money {\n if (moneys.length === 0) throw new CoinsError('sum requires at least one Money value');\n\n const currency = moneys[0]!.currency;\n\n for (let i = 1; i < moneys.length; i++) {\n if (moneys[i]!.currency !== currency) {\n throw new CurrencyMismatchError(currency, moneys[i]!.currency);\n }\n }\n\n return { amount: moneys.reduce((acc, m) => acc + m.amount, 0n), currency };\n}\n\n/**\n * Returns the smallest `Money` value from a non-empty array. Throws if currencies differ.\n *\n * @example\n * ```ts\n * min([money('3.00', 'USD'), money('1.00', 'USD'), money('2.00', 'USD')]) // $1.00\n * ```\n */\nexport function min(moneys: readonly Money[]): Money {\n if (moneys.length === 0) throw new CoinsError('min requires at least one Money value');\n\n let result = moneys[0]!;\n\n for (let i = 1; i < moneys.length; i++) result = compare(result, moneys[i]!) <= 0 ? result : moneys[i]!;\n\n return result;\n}\n\n/**\n * Returns the largest `Money` value from a non-empty array. Throws if currencies differ.\n *\n * @example\n * ```ts\n * max([money('1.00', 'USD'), money('3.00', 'USD'), money('2.00', 'USD')]) // $3.00\n * ```\n */\nexport function max(moneys: readonly Money[]): Money {\n if (moneys.length === 0) throw new CoinsError('max requires at least one Money value');\n\n let result = moneys[0]!;\n\n for (let i = 1; i < moneys.length; i++) result = compare(result, moneys[i]!) >= 0 ? result : moneys[i]!;\n\n return result;\n}\n\n/**\n * Splits `money` into `parts` equal shares without losing or gaining a single minor unit.\n * Equivalent to `allocate(money, Array.from({ length: parts }, () => 1))`.\n *\n * @throws {CoinsError} If `parts` is not a positive integer (≥ 1).\n *\n * @example\n * ```ts\n * splitEvenly(money('10.00', 'USD'), 3)\n * // → [$3.34, $3.33, $3.33]\n * ```\n */\nexport function splitEvenly(m: Money, parts: number): [Money, ...Money[]] {\n if (!Number.isInteger(parts) || parts <= 0) {\n throw new CoinsError('splitEvenly requires a positive integer number of parts');\n }\n\n return allocate(\n m,\n Array.from({ length: parts }, () => 1),\n );\n}\n\n/**\n * Clamps `m` to the inclusive range `[lower, upper]`.\n *\n * @throws {CurrencyMismatchError} If `m`, `lower`, or `upper` have different currencies.\n * @throws {CoinsError} If `lower > upper`.\n *\n * @example\n * ```ts\n * clamp(money('5.00', 'USD'), money('1.00', 'USD'), money('10.00', 'USD')) // $5.00\n * clamp(money('0.00', 'USD'), money('1.00', 'USD'), money('10.00', 'USD')) // $1.00\n * clamp(money('15.00', 'USD'), money('1.00', 'USD'), money('10.00', 'USD')) // $10.00\n * ```\n */\nexport function clamp(m: Money, lower: Money, upper: Money): Money {\n // Validate currency consistency upfront so the error always names the mismatch,\n // rather than surfacing mid-computation from min() or max().\n if (m.currency !== lower.currency || m.currency !== upper.currency) {\n throw new CurrencyMismatchError(m.currency, m.currency !== lower.currency ? lower.currency : upper.currency);\n }\n\n if (compare(lower, upper) > 0) {\n throw new CoinsError(\n `clamp: lower (${toDecimal(lower)} ${lower.currency}) must be <= upper (${toDecimal(upper)} ${upper.currency})`,\n );\n }\n\n return max([lower, min([m, upper])]);\n}\n\n// ─── Unary ───────────────────────────────────────────────────────────────────\n\n/** Returns the absolute value of `money`. Negative amounts become positive. */\nexport function abs(m: Money): Money {\n return { amount: m.amount < 0n ? -m.amount : m.amount, currency: m.currency };\n}\n\n/** Returns `money` with its sign flipped. */\nexport function negate(m: Money): Money {\n return { amount: -m.amount, currency: m.currency };\n}\n\n// ─── Comparison ──────────────────────────────────────────────────────────────\n\n/**\n * Compares two `Money` values. Returns `-1`, `0`, or `1`.\n * Throws if their currencies differ.\n */\nexport function compare(a: Money, b: Money): -1 | 0 | 1 {\n assertSameCurrency(a, b);\n\n if (a.amount < b.amount) return -1;\n\n if (a.amount > b.amount) return 1;\n\n return 0;\n}\n\n/**\n * Returns `true` if both `Money` values have the same currency and amount.\n * Returns `false` if currencies differ — safe to use in `.filter()` and conditional chains.\n */\nexport function isEqual(a: Money, b: Money): boolean {\n return a.currency === b.currency && a.amount === b.amount;\n}\n\n/** Returns `true` if `a` is strictly greater than `b`. Throws on currency mismatch. */\nexport function greaterThan(a: Money, b: Money): boolean {\n return compare(a, b) > 0;\n}\n\n/** Returns `true` if `a` is greater than or equal to `b`. Throws on currency mismatch. */\nexport function greaterThanOrEqual(a: Money, b: Money): boolean {\n return compare(a, b) >= 0;\n}\n\n/** Returns `true` if `a` is strictly less than `b`. Throws on currency mismatch. */\nexport function lessThan(a: Money, b: Money): boolean {\n return compare(a, b) < 0;\n}\n\n/** Returns `true` if `a` is less than or equal to `b`. Throws on currency mismatch. */\nexport function lessThanOrEqual(a: Money, b: Money): boolean {\n return compare(a, b) <= 0;\n}\n\n// ─── Predicates ──────────────────────────────────────────────────────────────\n\n/** Returns `true` if the amount is exactly zero. */\nexport function isZero(m: Money): boolean {\n return m.amount === 0n;\n}\n\n/** Returns `true` if the amount is strictly positive (> 0). */\nexport function isPositive(m: Money): boolean {\n return m.amount > 0n;\n}\n\n/** Returns `true` if the amount is strictly negative (< 0). */\nexport function isNegative(m: Money): boolean {\n return m.amount < 0n;\n}\n\n/** Returns `true` if the amount is zero or positive (>= 0). */\nexport function isNonNegative(m: Money): boolean {\n return m.amount >= 0n;\n}\n\n/** Returns `true` if the amount is zero or negative (<= 0). */\nexport function isNonPositive(m: Money): boolean {\n return m.amount <= 0n;\n}\n\n// ─── Serialization ───────────────────────────────────────────────────────────\n\n/**\n * Serializes a `Money` value to a plain-object JSON-safe form.\n * `amount` is a bigint string (e.g. `'123456'`) to avoid `JSON.stringify` throwing.\n *\n * @example\n * ```ts\n * toJSON(money('1234.56', 'USD')) // { amount: '123456', currency: 'USD' }\n * JSON.stringify(toJSON(price)) // '{\"amount\":\"123456\",\"currency\":\"USD\"}'\n * ```\n */\nexport function toJSON(m: Money): MoneyJSON {\n return { amount: String(m.amount), currency: m.currency };\n}\n\n/**\n * Deserializes a `MoneyJSON` object back into a `Money` value.\n * Validates the currency code and the amount string.\n *\n * @example\n * ```ts\n * fromJSON({ amount: '123456', currency: 'USD' }) // { amount: 123456n, currency: 'USD' }\n * ```\n */\nexport function fromJSON(json: MoneyJSON): Money {\n const validCurrency = validateCurrencyCode(json.currency);\n\n if (typeof json.amount !== 'string') {\n throw new CoinsError(\n `Invalid money amount in JSON: ${String(json.amount)} (expected an integer string, e.g. '123456')`,\n );\n }\n\n let amount: bigint;\n\n try {\n amount = BigInt(json.amount);\n } catch {\n throw new CoinsError(`Invalid money amount in JSON: \"${json.amount}\" (expected an integer string, e.g. '123456')`);\n }\n\n return { amount, currency: validCurrency };\n}\n\n/**\n * Serializes a `Money` value to a decimal string (e.g. `'1234.56'`).\n * Round-trips losslessly with `money()`.\n *\n * @example\n * ```ts\n * toDecimal(money('1234.56', 'USD')) // '1234.56'\n * toDecimal(money(5n, 'USD')) // '0.05'\n * toDecimal(money(1234n, 'JPY')) // '1234'\n * ```\n */\nexport function toDecimal(m: Money): string {\n const decimals = getCurrencyDecimals(m.currency);\n\n if (decimals === 0) return String(m.amount);\n\n const negative = m.amount < 0n;\n const abs = negative ? -m.amount : m.amount;\n const scale = pow10(decimals);\n const whole = abs / scale;\n const frac = (abs % scale).toString().padStart(decimals, '0');\n\n return `${negative ? '-' : ''}${whole}.${frac}`;\n}\n\n/**\n * Converts a `Money` value to a floating-point number.\n * Useful for charting and display; **not** for arithmetic (lossy).\n *\n * @example\n * ```ts\n * toNumber(money('1234.56', 'USD')) // 1234.56\n * ```\n */\nexport function toNumber(m: Money): number {\n const decimals = getCurrencyDecimals(m.currency);\n\n return Number(m.amount) / Number(pow10(decimals));\n}\n\n// ─── Rounding ─────────────────────────────────────────────────────────────────\n\n/**\n * Rounds a `Money` value to fewer decimal places than the currency's default.\n * `places` must be a non-negative integer in the range `0..currencyDecimals`.\n *\n * Useful for display purposes (e.g. rounding USD to whole dollars).\n * @throws {CoinsError} If `places` is out of the allowed range.\n *\n * @param mode Rounding mode. Defaults to `'half-away-from-zero'`.\n *\n * @example\n * ```ts\n * roundTo(money('1234.56', 'USD'), 0) // $1235 (whole dollars)\n * roundTo(money('1234.56', 'USD'), 1) // $1234.6\n * roundTo(money('1234.56', 'USD'), 1, 'floor') // $1234.5\n * roundTo(money('1234', 'JPY'), 0) // ¥1234 (no-op: JPY is already 0-decimal)\n * ```\n */\nexport function roundTo(m: Money, places: number, mode: RoundingMode = 'half-away-from-zero'): Money {\n if (!Number.isInteger(places) || places < 0) {\n throw new CoinsError(`roundTo: places must be a non-negative integer, got ${places}`);\n }\n\n const currencyDecimals = getCurrencyDecimals(m.currency);\n\n if (places > currencyDecimals) {\n throw new CoinsError(\n `roundTo: places (${places}) exceeds the decimal places for ${m.currency} (${currencyDecimals})`,\n );\n }\n\n if (places === currencyDecimals) return m;\n\n const drop = currencyDecimals - places;\n const divisor = pow10(drop);\n const negative = m.amount < 0n;\n const absAmount = negative ? -m.amount : m.amount;\n const quotient = absAmount / divisor;\n const remainder = absAmount % divisor;\n const rounded = applyRounding(quotient, remainder, divisor, mode, negative);\n\n return { amount: negative ? -rounded : rounded, currency: m.currency };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"coins.iife.js","names":[],"sources":["../src/errors.ts","../src/decimal.ts","../src/currency.ts","../src/money.ts","../src/aggregate.ts","../src/exchange.ts","../src/format.ts","../src/serialization.ts"],"sourcesContent":["export type CoinsErrorCode =\n | 'CURRENCY_MISMATCH'\n | 'DIVISION_BY_ZERO'\n | 'INVALID_ALLOCATION'\n | 'INVALID_CURRENCY'\n | 'INVALID_DECIMAL'\n | 'INVALID_MONEY'\n | 'INVALID_ROUNDING';\n\nexport class CoinsError extends Error {\n readonly code: CoinsErrorCode;\n\n static is(error: unknown): error is CoinsError {\n return error instanceof CoinsError;\n }\n\n constructor(code: CoinsErrorCode, message: string, options?: ErrorOptions) {\n super(message, options);\n this.code = code;\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class CurrencyMismatchError extends CoinsError {\n readonly expected: string;\n readonly received: string;\n\n constructor(expected: string, received: string) {\n super('CURRENCY_MISMATCH', `Currency mismatch: ${expected} and ${received}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\nexport class InvalidCurrencyError extends CoinsError {\n readonly value: unknown;\n\n constructor(value: unknown) {\n super('INVALID_CURRENCY', `Unsupported currency: \"${String(value)}\"`);\n this.value = value;\n }\n}\n","import type { Decimal, RoundingMode } from './types';\n\nimport { CoinsError } from './errors';\n\nconst DECIMAL = /^(-?)(\\d+)(?:\\.(\\d+))?$/;\nconst MAX_DECIMAL_DIGITS = 1000;\nconst MAX_DECIMAL_PLACES = 100;\n\nexport function decimal(value: string): Decimal {\n if (value.length > MAX_DECIMAL_DIGITS) throw new CoinsError('INVALID_DECIMAL', 'Decimal input is too long');\n\n const match = DECIMAL.exec(value);\n\n if (!match) throw new CoinsError('INVALID_DECIMAL', `Invalid decimal: \"${value}\"`);\n\n const fraction = (match[3] ?? '').replace(/0+$/, '');\n\n if (fraction.length > MAX_DECIMAL_PLACES) {\n throw new CoinsError('INVALID_DECIMAL', `Decimal precision cannot exceed ${MAX_DECIMAL_PLACES} places`);\n }\n\n const denominator = 10n ** BigInt(fraction.length);\n const unsigned = BigInt(match[2]!) * denominator + BigInt(fraction || '0');\n const numerator = match[1] === '-' && unsigned !== 0n ? -unsigned : unsigned;\n const divisor = gcd(numerator < 0n ? -numerator : numerator, denominator);\n\n return Object.freeze({ denominator: denominator / divisor, numerator: numerator / divisor }) as Decimal;\n}\n\nexport function roundDivision(numerator: bigint, denominator: bigint, mode: RoundingMode): bigint {\n if (denominator <= 0n) throw new CoinsError('INVALID_DECIMAL', 'Decimal denominator must be positive');\n\n const negative = numerator < 0n;\n const absolute = negative ? -numerator : numerator;\n const quotient = absolute / denominator;\n const remainder = absolute % denominator;\n\n if (remainder === 0n) return negative ? -quotient : quotient;\n\n const increment = (() => {\n switch (mode) {\n case 'awayFromZero':\n return true;\n case 'ceil':\n return !negative;\n case 'floor':\n return negative;\n case 'halfAwayFromZero':\n return remainder * 2n >= denominator;\n case 'halfEven': {\n const doubled = remainder * 2n;\n\n return doubled > denominator || (doubled === denominator && quotient % 2n !== 0n);\n }\n case 'towardZero':\n return false;\n }\n })();\n\n const result = increment ? quotient + 1n : quotient;\n\n return negative ? -result : result;\n}\n\nexport function toDecimalString(amount: bigint, minorUnit: number): string {\n const negative = amount < 0n;\n const absolute = negative ? -amount : amount;\n const scale = 10n ** BigInt(minorUnit);\n const whole = absolute / scale;\n\n if (minorUnit === 0) return `${negative ? '-' : ''}${whole}`;\n\n const fraction = (absolute % scale).toString().padStart(minorUnit, '0');\n\n return `${negative ? '-' : ''}${whole}.${fraction}`;\n}\n\nexport function gcd(left: bigint, right: bigint): bigint {\n let a = left;\n let b = right;\n\n while (b !== 0n) [a, b] = [b, a % b];\n\n return a === 0n ? 1n : a;\n}\n\nexport function lcm(left: bigint, right: bigint): bigint {\n return (left / gcd(left, right)) * right;\n}\n","import type { Currency, CurrencyCode } from './types';\n\nimport { CoinsError, InvalidCurrencyError } from './errors';\n\nconst builtins = new Map<string, Currency>();\nconst custom = new Map<string, Currency>();\n\nfunction builtin<C extends string>(code: C, minorUnit: number): Currency<C> {\n const definition = createDefinition(code, minorUnit);\n\n builtins.set(code, definition);\n\n return definition;\n}\n\nexport const USD = builtin('USD', 2);\nexport const EUR = builtin('EUR', 2);\nexport const GBP = builtin('GBP', 2);\nexport const JPY = builtin('JPY', 0);\nexport const KRW = builtin('KRW', 0);\nexport const BHD = builtin('BHD', 3);\nexport const KWD = builtin('KWD', 3);\n\n/** Custom definitions are explicit process configuration; built-ins remain immutable and separate. */\nexport function defineCurrency<C extends string>({ code, minorUnit }: { code: C; minorUnit: number }): Currency<C> {\n validateDefinition(code, minorUnit);\n\n const builtinDefinition = builtins.get(code);\n\n if (builtinDefinition) {\n if (builtinDefinition.minorUnit !== minorUnit) throw duplicateScaleError(code, builtinDefinition.minorUnit);\n\n return builtinDefinition as Currency<C>;\n }\n\n const existing = custom.get(code);\n\n if (existing) {\n if (existing.minorUnit !== minorUnit) throw duplicateScaleError(code, existing.minorUnit);\n\n return existing as Currency<C>;\n }\n\n const definition = createDefinition(code, minorUnit);\n\n custom.set(code, definition);\n\n return definition;\n}\n\nexport function currency(code: string): Currency {\n const definition = builtins.get(code) ?? custom.get(code);\n\n if (!definition) throw new InvalidCurrencyError(code);\n\n return definition;\n}\n\nexport function isCurrency(value: unknown): value is Currency {\n if (typeof value !== 'object' || value === null) return false;\n\n const candidate = value as Partial<Currency>;\n\n return (\n typeof candidate.code === 'string' &&\n (builtins.get(candidate.code) === value || custom.get(candidate.code) === value)\n );\n}\n\nexport function resolveBuiltinCurrency(code: string): Currency {\n const definition = builtins.get(code);\n\n if (!definition) throw new InvalidCurrencyError(code);\n\n return definition;\n}\n\nfunction createDefinition<C extends string>(code: C, minorUnit: number): Currency<C> {\n validateDefinition(code, minorUnit);\n\n return Object.freeze({ code: code as CurrencyCode<C>, minorUnit }) as Currency<C>;\n}\n\nfunction validateDefinition(code: string, minorUnit: number): void {\n if (!/^[A-Z]{3}$/.test(code)) {\n throw new CoinsError('INVALID_CURRENCY', `Currency code must be three uppercase letters: \"${code}\"`);\n }\n\n if (!Number.isInteger(minorUnit) || minorUnit < 0 || minorUnit > 6) {\n throw new CoinsError('INVALID_CURRENCY', `Currency \"${code}\" must have 0–6 minor-unit digits`);\n }\n}\n\nfunction duplicateScaleError(code: string, minorUnit: number): CoinsError {\n return new CoinsError('INVALID_CURRENCY', `Currency \"${code}\" already has ${minorUnit} minor-unit digits`);\n}\n","import type { Currency, Money, RoundingMode } from './types';\n\nimport { isCurrency } from './currency';\nimport { decimal, roundDivision, toDecimalString } from './decimal';\nimport { CoinsError, CurrencyMismatchError } from './errors';\n\nconst defaultRounding: RoundingMode = 'halfAwayFromZero';\n\nexport function money<C extends Currency>(amount: string, currency: C): Money<C>;\nexport function money<C extends Currency>(amount: string, currency: C, options: { rounding: RoundingMode }): Money<C>;\nexport function money<C extends Currency>(amount: bigint, currency: C, options: { unit: 'minor' }): Money<C>;\nexport function money<C extends Currency>(\n amount: bigint | string,\n currency: C,\n options?: { rounding: RoundingMode } | { unit: 'minor' },\n): Money<C> {\n assertCurrency(currency);\n\n if (typeof amount === 'bigint') {\n if (!options || !('unit' in options) || options.unit !== 'minor') {\n throw new CoinsError('INVALID_MONEY', \"Bigint amounts require { unit: 'minor' }\");\n }\n\n return createMoney(amount, currency);\n }\n\n if (options && 'unit' in options)\n throw new CoinsError('INVALID_MONEY', 'Decimal strings do not accept a unit option');\n\n const value = decimal(amount);\n const scaled = value.numerator * 10n ** BigInt(currency.minorUnit);\n const remainder = scaled % value.denominator;\n\n if (remainder !== 0n && !options) {\n throw new CoinsError('INVALID_MONEY', `Amount \"${amount}\" exceeds ${currency.code} precision; provide rounding`);\n }\n\n return createMoney(roundDivision(scaled, value.denominator, options?.rounding ?? defaultRounding), currency);\n}\n\nexport function parseMoney(value: unknown): Money {\n if (!isPlainDataObject(value)) throw new CoinsError('INVALID_MONEY', 'Money must be a plain data object');\n\n const descriptors = Object.getOwnPropertyDescriptors(value);\n const amountDescriptor = descriptors.amount;\n const currencyDescriptor = descriptors.currency;\n\n if (!isDataProperty(amountDescriptor) || !isDataProperty(currencyDescriptor)) {\n throw new CoinsError('INVALID_MONEY', 'Money properties must be plain data values');\n }\n\n if (typeof amountDescriptor.value !== 'bigint' || !isCurrency(currencyDescriptor.value)) {\n throw new CoinsError('INVALID_MONEY', 'Money must contain bigint amount and a registered currency');\n }\n\n return createMoney(amountDescriptor.value, currencyDescriptor.value);\n}\n\nexport function isMoney(value: unknown): value is Money {\n try {\n parseMoney(value);\n\n return true;\n } catch {\n return false;\n }\n}\n\nexport function add<C extends Currency>(left: Money<C>, right: Money<NoInfer<C>>): Money<C> {\n assertSameCurrency(left, right);\n\n return createMoney(left.amount + right.amount, left.currency);\n}\n\nexport function subtract<C extends Currency>(left: Money<C>, right: Money<NoInfer<C>>): Money<C> {\n assertSameCurrency(left, right);\n\n return createMoney(left.amount - right.amount, left.currency);\n}\n\nexport function multiply<C extends Currency>(\n value: Money<C>,\n factor: string,\n options: { rounding?: RoundingMode } = {},\n): Money<C> {\n assertMoney(value);\n\n const scalar = decimal(factor);\n\n return createMoney(\n roundDivision(value.amount * scalar.numerator, scalar.denominator, options.rounding ?? defaultRounding),\n value.currency,\n );\n}\n\nexport function divide<C extends Currency>(\n value: Money<C>,\n divisor: string,\n options: { rounding?: RoundingMode } = {},\n): Money<C> {\n assertMoney(value);\n\n const scalar = decimal(divisor);\n\n if (scalar.numerator === 0n) throw new CoinsError('DIVISION_BY_ZERO', 'Cannot divide money by zero');\n\n return createMoney(\n roundDivision(\n value.amount * scalar.denominator,\n scalar.numerator < 0n ? -scalar.numerator : scalar.numerator,\n options.rounding ?? defaultRounding,\n ) * (scalar.numerator < 0n ? -1n : 1n),\n value.currency,\n );\n}\n\nexport function compare<C extends Currency>(left: Money<C>, right: Money<NoInfer<C>>): -1 | 0 | 1 {\n assertSameCurrency(left, right);\n\n return left.amount === right.amount ? 0 : left.amount < right.amount ? -1 : 1;\n}\n\nexport function abs<C extends Currency>(value: Money<C>): Money<C> {\n assertMoney(value);\n\n return createMoney(value.amount < 0n ? -value.amount : value.amount, value.currency);\n}\n\nexport function negate<C extends Currency>(value: Money<C>): Money<C> {\n assertMoney(value);\n\n return createMoney(-value.amount, value.currency);\n}\n\nexport function round<C extends Currency>(\n value: Money<C>,\n options: { fractionDigits: number; rounding?: RoundingMode },\n): Money<C> {\n assertMoney(value);\n\n const { fractionDigits, rounding = defaultRounding } = options;\n\n if (!Number.isInteger(fractionDigits) || fractionDigits < 0 || fractionDigits > value.currency.minorUnit) {\n throw new CoinsError('INVALID_ROUNDING', `fractionDigits must be an integer from 0 to ${value.currency.minorUnit}`);\n }\n\n const factor = 10n ** BigInt(value.currency.minorUnit - fractionDigits);\n\n return createMoney(roundDivision(value.amount, factor, rounding) * factor, value.currency);\n}\n\nexport function toDecimal(value: Money): string {\n assertMoney(value);\n\n return toDecimalString(value.amount, value.currency.minorUnit);\n}\n\nexport function withMinor<C extends Currency>(amount: bigint, currency: C): Money<C> {\n assertCurrency(currency);\n\n return createMoney(amount, currency);\n}\n\nfunction createMoney<C extends Currency>(amount: bigint, currency: C): Money<C> {\n return Object.freeze({ amount, currency }) as Money<C>;\n}\n\nfunction assertMoney(value: Money): void {\n if (!Object.isFrozen(value) || !isCurrency(value.currency)) {\n throw new CoinsError('INVALID_MONEY', 'Money must be a canonical Coins value');\n }\n}\n\nfunction assertSameCurrency(left: Money, right: Money): void {\n assertMoney(left);\n assertMoney(right);\n\n if (left.currency !== right.currency) {\n throw new CurrencyMismatchError(left.currency.code, right.currency.code);\n }\n}\n\nfunction assertCurrency(value: Currency): void {\n if (!isCurrency(value)) throw new CoinsError('INVALID_CURRENCY', 'Money requires a registered currency');\n}\n\nfunction isPlainDataObject(value: unknown): value is Record<PropertyKey, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n\n const prototype = Object.getPrototypeOf(value);\n\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction isDataProperty(\n descriptor: PropertyDescriptor | undefined,\n): descriptor is PropertyDescriptor & { value: unknown } {\n return (\n descriptor !== undefined && 'value' in descriptor && descriptor.get === undefined && descriptor.set === undefined\n );\n}\n","import type { Currency, Money } from './types';\n\nimport { decimal, lcm } from './decimal';\nimport { CoinsError, CurrencyMismatchError } from './errors';\nimport { compare, isMoney, withMinor } from './money';\n\nexport function sum<C extends Currency>(values: Iterable<Money<C>>, options: { currency: C }): Money<C> {\n let amount = 0n;\n\n for (const value of values) {\n if (!isMoney(value)) throw new CoinsError('INVALID_MONEY', 'sum() requires canonical money values');\n\n if (value.currency !== options.currency)\n throw new CurrencyMismatchError(options.currency.code, value.currency.code);\n\n amount += value.amount;\n }\n\n return withMinor(amount, options.currency);\n}\n\nexport function allocate<C extends Currency>(value: Money<C>, count: number): Money<C>[];\nexport function allocate<C extends Currency>(value: Money<C>, weights: readonly string[]): Money<C>[];\nexport function allocate<C extends Currency>(value: Money<C>, weightsOrCount: number | readonly string[]): Money<C>[] {\n if (!isMoney(value)) throw new CoinsError('INVALID_MONEY', 'allocate() requires canonical money');\n\n if (typeof weightsOrCount === 'number') return allocateEvenly(value, weightsOrCount);\n\n if (weightsOrCount.length === 0) throw new CoinsError('INVALID_ALLOCATION', 'Allocation weights cannot be empty');\n\n const weights = weightsOrCount.map(decimal);\n\n if (weights.some((weight) => weight.numerator < 0n)) {\n throw new CoinsError('INVALID_ALLOCATION', 'Allocation weights cannot be negative');\n }\n\n const commonDenominator = weights.reduce((result, weight) => lcm(result, weight.denominator), 1n);\n const scaledWeights = weights.map((weight) => weight.numerator * (commonDenominator / weight.denominator));\n const totalWeight = scaledWeights.reduce((result, weight) => result + weight, 0n);\n\n if (totalWeight === 0n)\n throw new CoinsError('INVALID_ALLOCATION', 'Allocation weights must include a positive value');\n\n const sign = value.amount < 0n ? -1n : 1n;\n const absolute = value.amount < 0n ? -value.amount : value.amount;\n const shares = scaledWeights.map((weight) => (absolute * weight) / totalWeight);\n let remainder = absolute - shares.reduce((result, amount) => result + amount, 0n);\n const ranked = scaledWeights\n .map((weight, index) => ({ index, remainder: (absolute * weight) % totalWeight }))\n .sort((left, right) =>\n left.remainder === right.remainder ? left.index - right.index : left.remainder > right.remainder ? -1 : 1,\n );\n\n for (const entry of ranked) {\n if (remainder === 0n) break;\n\n shares[entry.index]! += 1n;\n remainder -= 1n;\n }\n\n return shares.map((amount) => withMinor(amount * sign, value.currency));\n}\n\nexport function clamp<C extends Currency>(\n value: Money<C>,\n options: { max: Money<NoInfer<C>>; min: Money<NoInfer<C>> },\n): Money<C> {\n if (compare(options.min, options.max) === 1) {\n throw new CoinsError('INVALID_ALLOCATION', 'Clamp minimum cannot exceed maximum');\n }\n\n return compare(value, options.min) === -1 ? options.min : compare(value, options.max) === 1 ? options.max : value;\n}\n\nfunction allocateEvenly<C extends Currency>(value: Money<C>, countValue: number): Money<C>[] {\n if (!Number.isInteger(countValue) || countValue < 1) {\n throw new CoinsError('INVALID_ALLOCATION', 'Allocation count must be a positive integer');\n }\n\n const count = BigInt(countValue);\n const sign = value.amount < 0n ? -1n : 1n;\n const absolute = value.amount < 0n ? -value.amount : value.amount;\n const base = absolute / count;\n const remainder = absolute % count;\n\n return Array.from({ length: countValue }, (_, index) =>\n withMinor((base + (BigInt(index) < remainder ? 1n : 0n)) * sign, value.currency),\n );\n}\n","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","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","import type { Currency, Money, MoneyJSON } from './types';\n\nimport { resolveBuiltinCurrency } from './currency';\nimport { CoinsError } from './errors';\nimport { money } from './money';\n\nconst INTEGER = /^(?:0|-[1-9]\\d*|[1-9]\\d*)$/;\nconst KEYS = ['amount', 'currency', 'unit'] as const;\n\nexport function toJSON(value: Money): MoneyJSON {\n return { amount: value.amount.toString(), currency: value.currency.code, unit: 'minor' };\n}\n\nexport function parseMoneyJSON(value: unknown, options: { currency?: (code: string) => Currency } = {}): Money {\n try {\n const payload = readPayload(value);\n const resolveCurrency = options.currency ?? resolveBuiltinCurrency;\n\n return money(BigInt(payload.amount), resolveCurrency(payload.currency), { unit: 'minor' });\n } catch (error) {\n throw new CoinsError('INVALID_MONEY', 'Invalid Money JSON', { cause: error });\n }\n}\n\nfunction readPayload(value: unknown): MoneyJSON {\n if (typeof value !== 'object' || value === null || Object.getPrototypeOf(value) !== Object.prototype) {\n throw new TypeError('Money JSON must be a plain object');\n }\n\n const descriptors = Object.getOwnPropertyDescriptors(value);\n const ownKeys = Reflect.ownKeys(value);\n\n if (ownKeys.length !== KEYS.length || !KEYS.every((key) => ownKeys.includes(key))) {\n throw new TypeError('Money JSON must contain only amount, currency, and unit');\n }\n\n for (const key of KEYS) {\n const descriptor = descriptors[key];\n\n if (!descriptor || !('value' in descriptor) || descriptor.get || descriptor.set) {\n throw new TypeError(`Money JSON property \"${key}\" must be a data value`);\n }\n }\n\n const amount = descriptors.amount!.value;\n const currency = descriptors.currency!.value;\n const unit = descriptors.unit!.value;\n\n if (typeof amount !== 'string' || !INTEGER.test(amount))\n throw new TypeError('Money JSON amount must be a canonical integer string');\n\n if (typeof currency !== 'string' || unit !== 'minor') throw new TypeError('Money JSON currency/unit are invalid');\n\n return { amount, currency, unit };\n}\n"],"mappings":"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"}
package/dist/coins.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{expected;received;constructor(e,t){super(`Currency mismatch: ${e} and ${t}`),this.expected=e,this.received=t}},n=class extends e{code;constructor(e){super(`Invalid ISO 4217 currency code: "${e}"`),this.code=e}};function r(e){let t=new Map;return{get:e=>t.get(e),set(n,r){t.size>=e&&t.delete(t.keys().next().value),t.set(n,r)}}}var i=r(512);function a(e){let t=i.get(e);if(t!==void 0)return t;let r;try{r=new Intl.NumberFormat(`en`,{currency:e,style:`currency`}).resolvedOptions().maximumFractionDigits}catch{throw new n(e)}if(r===void 0)throw new n(e);return i.set(e,r),r}function o(e){return a(e),e}function s(e){return 10n**BigInt(e)}var c=/^-?\d+(\.\d+)?$/,l=/^(-?\d+\.?\d*)[eE]([+-]?\d+)$/,u=1e3;function d(t){let n=l.exec(t),r=n[1],i=parseInt(n[2],10);if(Math.abs(i)>u)throw new e(`Scientific notation exponent too large: "${t}" (max ±${u})`);let a=r.startsWith(`-`),o=a?r.slice(1):r,s=o.indexOf(`.`),c=s===-1?o:o.replace(`.`,``),d=i-(s===-1?0:o.length-s-1),f;if(d>=0)f=c+`0`.repeat(d);else{let e=c.length+d;f=e<=0?`0.`+`0`.repeat(-e)+c:c.slice(0,e)+`.`+c.slice(e)}return a?`-${f}`:f}function f(t){let n=l.test(t)?d(t):t;if(!c.test(n))throw new e(`Invalid decimal string: "${t}"`);let r=n.startsWith(`-`),i=r?n.slice(1):n,a=i.indexOf(`.`),o=a===-1?i:i.slice(0,a),u=a===-1?``:i.slice(a+1),f=s(u.length);return{denominator:f,negative:r,numerator:BigInt(o||`0`)*f+BigInt(u||`0`)}}function p(t,n,r,i,a=!1){if(n===0n)return t;switch(i){case`ceiling`:return a?t:t+1n;case`down`:return t;case`floor`:return a?t+1n:t;case`half-away-from-zero`:return n*2n>=r?t+1n:t;case`half-even`:{let e=n*2n;return e<r?t:e>r?t+1n:t%2n==0n?t:t+1n}case`up`:return t+1n;default:throw new e(`Unknown rounding mode: ${String(i)}`)}}function m(n,r,i=`half-away-from-zero`){if(n.currency!==r.from)throw new t(n.currency,r.from);if(r.rate===``)throw new e(`Exchange rate must be a non-empty decimal string`);let{denominator:a,negative:s,numerator:c}=f(String(r.rate));if(s)throw new e(`Exchange rate must be non-negative`);let l=n.amount<0n,u=(n.amount<0n?-n.amount:n.amount)*c,d=p(u/a,u%a,a,i,l);return{amount:l?-d:d,currency:o(r.to)}}var h=r(512),g=r(32);function _(t,n){let{locale:r=`en-US`,maximumFractionDigits:i,minimumFractionDigits:o,style:c=`symbol`}=n,l=a(t.currency),u=b(`maximumFractionDigits`,i,l),d=b(`minimumFractionDigits`,o,Math.min(l,u));if(d>u)throw new e(`minimumFractionDigits must be less than or equal to maximumFractionDigits`);let f=ee(t.amount,l,u),p=f<0n,m=p?-f:f,h=s(u),g=m/h,_=x(u===0?``:(m%h).toString().padStart(u,`0`),d),v=S(r,t.currency,c,p);return{frac:_,intStr:C(r).format(g),negative:p,template:v}}function v(e,t={}){return y(e,t).map(e=>e.value).join(``)}function y(e,t={}){let{frac:n,intStr:r,template:i}=_(e,t),a=n.length>0,o=[],s=!1;for(let e of i)if(e.type!==`group`){if(e.type===`integer`){s||=(o.push({type:`integer`,value:r}),!0);continue}if(e.type===`decimal`){a&&o.push({type:`decimal`,value:e.value});continue}if(e.type===`fraction`){a&&o.push({type:`fraction`,value:n});continue}e.type===`currency`?o.push({type:`currency`,value:e.value}):e.type===`minusSign`?o.push({type:`minusSign`,value:e.value}):o.push({type:`literal`,value:e.value})}return o}function b(t,n,r){if(n==null)return r;if(!Number.isInteger(n)||n<0)throw new e(`${t} must be a non-negative integer`);return n}function ee(e,t,n){if(t===n)return e;if(n>t)return e*s(n-t);let r=e<0n,i=r?-e:e,a=s(t-n),o=p(i/a,i%a,a,`half-away-from-zero`);return r?-o:o}function x(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 S(e,t,n,r){let i=[e,t,n,r?`neg`:`pos`].join(`\0`),a=h.get(i);if(a!==void 0)return a;let o=new Intl.NumberFormat(e,{currency:t,currencyDisplay:n,maximumFractionDigits:1,minimumFractionDigits:1,style:`currency`}).formatToParts(r?-1.1:1.1);return h.set(i,o),o}function C(e){let t=g.get(e);if(t!==void 0)return t;let n=new Intl.NumberFormat(e,{maximumFractionDigits:0,minimumFractionDigits:0,useGrouping:!0});return g.set(e,n),n}var w=!globalThis.__COINS_PROD__;function T(e){w&&console.warn(`[@vielzeug/coins] ${e}`)}function E(e,t){let n=o(t);if(typeof e==`bigint`)return{amount:e,currency:n};if(typeof e==`number`){let t=a(n),r=String(e),i=l.test(r)?d(r):r,o=i.indexOf(`.`),s=o===-1?0:i.length-o-1;return s>t&&T(`money(): number input "${e}" has more decimal places (${s}) than ${n} supports (${t}). Use a decimal string to avoid IEEE-754 precision loss.`),{amount:O(r,t),currency:n}}return{amount:O(String(e),a(n)),currency:n}}function D(e,n){if(e.currency!==n.currency)throw new t(e.currency,n.currency)}function O(e,t){let{denominator:n,negative:r,numerator:i}=f(e),a=i*s(t),o=p(a/n,a%n,n,`half-away-from-zero`);return r?-o:o}function k(e,t){return{amount:t,currency:e.currency}}function A(e){if(typeof e!=`object`||!e)return!1;let t=e;return Object.hasOwn(t,`amount`)&&typeof t.amount==`bigint`&&Object.hasOwn(t,`currency`)&&typeof t.currency==`string`}function j(e,t){return D(e,t),{amount:e.amount+t.amount,currency:e.currency}}function M(e,t){return D(e,t),{amount:e.amount-t.amount,currency:e.currency}}function N(e,t,n=`half-away-from-zero`){let{denominator:r,negative:i,numerator:a}=f(String(t)),o=e.amount<0n!==i,s=(e.amount<0n?-e.amount:e.amount)*a,c=p(s/r,s%r,r,n,o);return{amount:o?-c:c,currency:e.currency}}function P(t,n,r=`half-away-from-zero`){let{denominator:i,negative:a,numerator:o}=f(String(n));if(o===0n)throw new e(`Division by zero`);let s=t.amount<0n!==a,c=(t.amount<0n?-t.amount:t.amount)*i,l=p(c/o,c%o,o,r,s);return{amount:s?-l:l,currency:t.currency}}function F(t,n){if(n.length===0)throw new e(`allocate requires at least one ratio`);let r=n.map(e=>f(String(e)));if(r.some(e=>e.negative))throw new e(`All ratios must be non-negative`);if(!r.some(e=>e.numerator>0n))throw new e(`At least one ratio must be positive`);let i=t.amount<0n,a=i?-t.amount:t.amount,o=r.reduce((e,t)=>t.denominator>e?t.denominator:e,1n),s=r.map(e=>e.numerator*(o/e.denominator)),c=s.reduce((e,t)=>e+t,0n),l=[],u=[];for(let e of s){let t=a*e;l.push(t/c),u.push(t%c)}let d=n.map((e,t)=>t).sort((e,t)=>{let n=u[t]-u[e];return n>0n?1:n<0n?-1:0}),p=[...l],m=a-l.reduce((e,t)=>e+t,0n);for(let e=0;m>0n;e++,m--)p[d[e]]+=1n;return p.map(e=>({amount:i?-e:e,currency:t.currency}))}function I(n){if(n.length===0)throw new e(`sum requires at least one Money value`);let r=n[0].currency;for(let e=1;e<n.length;e++)if(n[e].currency!==r)throw new t(r,n[e].currency);return{amount:n.reduce((e,t)=>e+t.amount,0n),currency:r}}function L(t){if(t.length===0)throw new e(`min requires at least one Money value`);let n=t[0];for(let e=1;e<t.length;e++)n=U(n,t[e])<=0?n:t[e];return n}function R(t){if(t.length===0)throw new e(`max requires at least one Money value`);let n=t[0];for(let e=1;e<t.length;e++)n=U(n,t[e])>=0?n:t[e];return n}function z(t,n){if(!Number.isInteger(n)||n<=0)throw new e(`splitEvenly requires a positive integer number of parts`);return F(t,Array.from({length:n},()=>1))}function B(n,r,i){if(n.currency!==r.currency||n.currency!==i.currency)throw new t(n.currency,n.currency===r.currency?i.currency:r.currency);if(U(r,i)>0)throw new e(`clamp: lower (${$(r)} ${r.currency}) must be <= upper (${$(i)} ${i.currency})`);return R([r,L([n,i])])}function V(e){return{amount:e.amount<0n?-e.amount:e.amount,currency:e.currency}}function H(e){return{amount:-e.amount,currency:e.currency}}function U(e,t){return D(e,t),e.amount<t.amount?-1:+(e.amount>t.amount)}function W(e,t){return e.currency===t.currency&&e.amount===t.amount}function G(e,t){return U(e,t)>0}function K(e,t){return U(e,t)>=0}function q(e,t){return U(e,t)<0}function J(e,t){return U(e,t)<=0}function Y(e){return e.amount===0n}function X(e){return e.amount>0n}function Z(e){return e.amount<0n}function Q(e){return e.amount>=0n}function te(e){return e.amount<=0n}function ne(e){return{amount:String(e.amount),currency:e.currency}}function re(t){let n=o(t.currency);if(typeof t.amount!=`string`)throw new e(`Invalid money amount in JSON: ${String(t.amount)} (expected an integer string, e.g. '123456')`);let r;try{r=BigInt(t.amount)}catch{throw new e(`Invalid money amount in JSON: "${t.amount}" (expected an integer string, e.g. '123456')`)}return{amount:r,currency:n}}function $(e){let t=a(e.currency);if(t===0)return String(e.amount);let n=e.amount<0n,r=n?-e.amount:e.amount,i=s(t),o=r/i,c=(r%i).toString().padStart(t,`0`);return`${n?`-`:``}${o}.${c}`}function ie(e){let t=a(e.currency);return Number(e.amount)/Number(s(t))}function ae(t,n,r=`half-away-from-zero`){if(!Number.isInteger(n)||n<0)throw new e(`roundTo: places must be a non-negative integer, got ${n}`);let i=a(t.currency);if(n>i)throw new e(`roundTo: places (${n}) exceeds the decimal places for ${t.currency} (${i})`);if(n===i)return t;let o=s(i-n),c=t.amount<0n,l=c?-t.amount:t.amount,u=p(l/o,l%o,o,r,c);return{amount:c?-u:u,currency:t.currency}}export{e as CoinsError,t as CurrencyMismatchError,n as InvalidCurrencyError,V as abs,j as add,F as allocate,B as clamp,U as compare,P as divide,m as exchange,v as format,y as formatParts,re as fromJSON,a as getCurrencyDecimals,G as greaterThan,K as greaterThanOrEqual,W as isEqual,A as isMoney,Z as isNegative,Q as isNonNegative,te as isNonPositive,X as isPositive,Y as isZero,q as lessThan,J as lessThanOrEqual,R as max,L as min,E as money,N as multiply,H as negate,ae as roundTo,z as splitEvenly,M as subtract,I as sum,$ as toDecimal,ne as toJSON,ie as toNumber,o as validateCurrencyCode,k as withAmount};
1
+ var e=class e extends Error{code;static is(t){return t instanceof e}constructor(e,t,n){super(t,n),this.code=e,this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{expected;received;constructor(e,t){super(`CURRENCY_MISMATCH`,`Currency mismatch: ${e} and ${t}`),this.expected=e,this.received=t}},n=class extends e{value;constructor(e){super(`INVALID_CURRENCY`,`Unsupported currency: "${String(e)}"`),this.value=e}},r=/^(-?)(\d+)(?:\.(\d+))?$/,i=1e3,a=100;function o(t){if(t.length>i)throw new e(`INVALID_DECIMAL`,`Decimal input is too long`);let n=r.exec(t);if(!n)throw new e(`INVALID_DECIMAL`,`Invalid decimal: "${t}"`);let o=(n[3]??``).replace(/0+$/,``);if(o.length>a)throw new e(`INVALID_DECIMAL`,`Decimal precision cannot exceed ${a} places`);let s=10n**BigInt(o.length),c=BigInt(n[2])*s+BigInt(o||`0`),u=n[1]===`-`&&c!==0n?-c:c,d=l(u<0n?-u:u,s);return Object.freeze({denominator:s/d,numerator:u/d})}function s(t,n,r){if(n<=0n)throw new e(`INVALID_DECIMAL`,`Decimal denominator must be positive`);let i=t<0n,a=i?-t:t,o=a/n,s=a%n;if(s===0n)return i?-o:o;let c=(()=>{switch(r){case`awayFromZero`:return!0;case`ceil`:return!i;case`floor`:return i;case`halfAwayFromZero`:return s*2n>=n;case`halfEven`:{let e=s*2n;return e>n||e===n&&o%2n!=0n}case`towardZero`:return!1}})()?o+1n:o;return i?-c:c}function c(e,t){let n=e<0n,r=n?-e:e,i=10n**BigInt(t),a=r/i;if(t===0)return`${n?`-`:``}${a}`;let o=(r%i).toString().padStart(t,`0`);return`${n?`-`:``}${a}.${o}`}function l(e,t){let n=e,r=t;for(;r!==0n;)[n,r]=[r,n%r];return n===0n?1n:n}function u(e,t){return e/l(e,t)*t}var d=new Map,f=new Map;function p(e,t){let n=w(e,t);return d.set(e,n),n}var m=p(`USD`,2),h=p(`EUR`,2),g=p(`GBP`,2),_=p(`JPY`,0),ee=p(`KRW`,0),v=p(`BHD`,3),y=p(`KWD`,3);function b({code:e,minorUnit:t}){T(e,t);let n=d.get(e);if(n){if(n.minorUnit!==t)throw E(e,n.minorUnit);return n}let r=f.get(e);if(r){if(r.minorUnit!==t)throw E(e,r.minorUnit);return r}let i=w(e,t);return f.set(e,i),i}function x(e){let t=d.get(e)??f.get(e);if(!t)throw new n(e);return t}function S(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.code==`string`&&(d.get(t.code)===e||f.get(t.code)===e)}function C(e){let t=d.get(e);if(!t)throw new n(e);return t}function w(e,t){return T(e,t),Object.freeze({code:e,minorUnit:t})}function T(t,n){if(!/^[A-Z]{3}$/.test(t))throw new e(`INVALID_CURRENCY`,`Currency code must be three uppercase letters: "${t}"`);if(!Number.isInteger(n)||n<0||n>6)throw new e(`INVALID_CURRENCY`,`Currency "${t}" must have 0–6 minor-unit digits`)}function E(t,n){return new e(`INVALID_CURRENCY`,`Currency "${t}" already has ${n} minor-unit digits`)}var D=`halfAwayFromZero`;function O(t,n,r){if(z(n),typeof t==`bigint`){if(!r||!(`unit`in r)||r.unit!==`minor`)throw new e(`INVALID_MONEY`,`Bigint amounts require { unit: 'minor' }`);return I(t,n)}if(r&&`unit`in r)throw new e(`INVALID_MONEY`,`Decimal strings do not accept a unit option`);let i=o(t),a=i.numerator*10n**BigInt(n.minorUnit);if(a%i.denominator!==0n&&!r)throw new e(`INVALID_MONEY`,`Amount "${t}" exceeds ${n.code} precision; provide rounding`);return I(s(a,i.denominator,r?.rounding??D),n)}function k(t){if(!B(t))throw new e(`INVALID_MONEY`,`Money must be a plain data object`);let n=Object.getOwnPropertyDescriptors(t),r=n.amount,i=n.currency;if(!V(r)||!V(i))throw new e(`INVALID_MONEY`,`Money properties must be plain data values`);if(typeof r.value!=`bigint`||!S(i.value))throw new e(`INVALID_MONEY`,`Money must contain bigint amount and a registered currency`);return I(r.value,i.value)}function A(e){try{return k(e),!0}catch{return!1}}function te(e,t){return R(e,t),I(e.amount+t.amount,e.currency)}function ne(e,t){return R(e,t),I(e.amount-t.amount,e.currency)}function re(e,t,n={}){L(e);let r=o(t);return I(s(e.amount*r.numerator,r.denominator,n.rounding??D),e.currency)}function ie(t,n,r={}){L(t);let i=o(n);if(i.numerator===0n)throw new e(`DIVISION_BY_ZERO`,`Cannot divide money by zero`);return I(s(t.amount*i.denominator,i.numerator<0n?-i.numerator:i.numerator,r.rounding??D)*(i.numerator<0n?-1n:1n),t.currency)}function j(e,t){return R(e,t),e.amount===t.amount?0:e.amount<t.amount?-1:1}function ae(e){return L(e),I(e.amount<0n?-e.amount:e.amount,e.currency)}function M(e){return L(e),I(-e.amount,e.currency)}function N(t,n){L(t);let{fractionDigits:r,rounding:i=D}=n;if(!Number.isInteger(r)||r<0||r>t.currency.minorUnit)throw new e(`INVALID_ROUNDING`,`fractionDigits must be an integer from 0 to ${t.currency.minorUnit}`);let a=10n**BigInt(t.currency.minorUnit-r);return I(s(t.amount,a,i)*a,t.currency)}function P(e){return L(e),c(e.amount,e.currency.minorUnit)}function F(e,t){return z(t),I(e,t)}function I(e,t){return Object.freeze({amount:e,currency:t})}function L(t){if(!Object.isFrozen(t)||!S(t.currency))throw new e(`INVALID_MONEY`,`Money must be a canonical Coins value`)}function R(e,n){if(L(e),L(n),e.currency!==n.currency)throw new t(e.currency.code,n.currency.code)}function z(t){if(!S(t))throw new e(`INVALID_CURRENCY`,`Money requires a registered currency`)}function B(e){if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null}function V(e){return e!==void 0&&`value`in e&&e.get===void 0&&e.set===void 0}function H(n,r){let i=0n;for(let a of n){if(!A(a))throw new e(`INVALID_MONEY`,`sum() requires canonical money values`);if(a.currency!==r.currency)throw new t(r.currency.code,a.currency.code);i+=a.amount}return F(i,r.currency)}function U(t,n){if(!A(t))throw new e(`INVALID_MONEY`,`allocate() requires canonical money`);if(typeof n==`number`)return G(t,n);if(n.length===0)throw new e(`INVALID_ALLOCATION`,`Allocation weights cannot be empty`);let r=n.map(o);if(r.some(e=>e.numerator<0n))throw new e(`INVALID_ALLOCATION`,`Allocation weights cannot be negative`);let i=r.reduce((e,t)=>u(e,t.denominator),1n),a=r.map(e=>e.numerator*(i/e.denominator)),s=a.reduce((e,t)=>e+t,0n);if(s===0n)throw new e(`INVALID_ALLOCATION`,`Allocation weights must include a positive value`);let c=t.amount<0n?-1n:1n,l=t.amount<0n?-t.amount:t.amount,d=a.map(e=>l*e/s),f=l-d.reduce((e,t)=>e+t,0n),p=a.map((e,t)=>({index:t,remainder:l*e%s})).sort((e,t)=>e.remainder===t.remainder?e.index-t.index:e.remainder>t.remainder?-1:1);for(let e of p){if(f===0n)break;d[e.index]+=1n,f-=1n}return d.map(e=>F(e*c,t.currency))}function W(t,n){if(j(n.min,n.max)===1)throw new e(`INVALID_ALLOCATION`,`Clamp minimum cannot exceed maximum`);return j(t,n.min)===-1?n.min:j(t,n.max)===1?n.max:t}function G(t,n){if(!Number.isInteger(n)||n<1)throw new e(`INVALID_ALLOCATION`,`Allocation count must be a positive integer`);let r=BigInt(n),i=t.amount<0n?-1n:1n,a=t.amount<0n?-t.amount:t.amount,o=a/r,s=a%r;return Array.from({length:n},(e,n)=>F((o+(BigInt(n)<s?1n:0n))*i,t.currency))}function K({from:t,to:n,value:r}){if(!S(t)||!S(n))throw new e(`INVALID_CURRENCY`,`Exchange rate requires registered currencies`);let i=o(r);if(i.numerator<0n)throw new e(`INVALID_DECIMAL`,`Exchange rates cannot be negative`);return Object.freeze({from:t,to:n,value:i})}function q(n,r,i={}){if(!A(n)||!oe(r))throw new e(`INVALID_MONEY`,`Exchange requires canonical money and rate values`);if(n.currency!==r.from)throw new t(n.currency.code,r.from.code);return F(s(n.amount*r.value.numerator*10n**BigInt(r.to.minorUnit),r.value.denominator*10n**BigInt(r.from.minorUnit),i.rounding??`halfAwayFromZero`),r.to)}function oe(e){if(typeof e!=`object`||!e||!Object.isFrozen(e))return!1;let t=e;return S(t.from)&&S(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}var J=20,Y=new Map,X=new Map;function se(e,t={}){return Z(e,t).map(e=>e.value).join(``)}function Z(t,n={}){if(!A(t))throw new e(`INVALID_MONEY`,`format() requires canonical money`);let{locale:r=`en-US`,maximumFractionDigits:i=t.currency.minorUnit,minimumFractionDigits:a=t.currency.minorUnit,style:o=`symbol`}=n;ce(a,i);let c=10n**BigInt(i),l=i>=t.currency.minorUnit?t.amount*10n**BigInt(i-t.currency.minorUnit):s(t.amount,10n**BigInt(t.currency.minorUnit-i),`halfAwayFromZero`),u=l<0n,d=u?-l:l,f=d/c,p=(i===0?``:(d%c).toString().padStart(i,`0`)).replace(RegExp(`0{0,${Math.max(0,i-a)}}$`),``),m=le(r,t.currency.code,o,u),h=[],g=!1;for(let e of m)e.type!==`group`&&(e.type===`integer`?g||=(h.push({type:`integer`,value:ue(r).format(f)}),!0):e.type===`decimal`?p&&h.push({type:`decimal`,value:e.value}):e.type===`fraction`?p&&h.push({type:`fraction`,value:p}):e.type===`currency`||e.type===`minusSign`||e.type===`plusSign`?h.push({type:e.type,value:e.value}):h.push({type:`literal`,value:e.value}));return h}function ce(t,n){if(!Number.isInteger(n)||!Number.isInteger(t)||t<0||n<t||n>J)throw new e(`INVALID_ROUNDING`,`Fraction digits must be integers satisfying 0 ≤ minimum ≤ maximum ≤ ${J}`)}function le(t,n,r,i){let a=`${t}\0${n}\0${r}\0${i}`,o=X.get(a);if(o)return o;try{let e=new Intl.NumberFormat(t,{currency:n,currencyDisplay:r,maximumFractionDigits:1,minimumFractionDigits:1,style:`currency`}).formatToParts(i?-1.1:1.1);return X.set(a,e),e}catch(r){throw new e(`INVALID_CURRENCY`,`Cannot format currency "${n}" for locale "${t}"`,{cause:r})}}function ue(t){let n=Y.get(t);if(n)return n;try{let e=new Intl.NumberFormat(t,{maximumFractionDigits:0,useGrouping:!0});return Y.set(t,e),e}catch(n){throw new e(`INVALID_ROUNDING`,`Cannot create number formatter for locale "${t}"`,{cause:n})}}var Q=/^(?:0|-[1-9]\d*|[1-9]\d*)$/,$=[`amount`,`currency`,`unit`];function de(e){return{amount:e.amount.toString(),currency:e.currency.code,unit:`minor`}}function fe(t,n={}){try{let e=pe(t),r=n.currency??C;return O(BigInt(e.amount),r(e.currency),{unit:`minor`})}catch(t){throw new e(`INVALID_MONEY`,`Invalid Money JSON`,{cause:t})}}function pe(e){if(typeof e!=`object`||!e||Object.getPrototypeOf(e)!==Object.prototype)throw TypeError(`Money JSON must be a plain object`);let t=Object.getOwnPropertyDescriptors(e),n=Reflect.ownKeys(e);if(n.length!==$.length||!$.every(e=>n.includes(e)))throw TypeError(`Money JSON must contain only amount, currency, and unit`);for(let e of $){let n=t[e];if(!n||!(`value`in n)||n.get||n.set)throw TypeError(`Money JSON property "${e}" must be a data value`)}let r=t.amount.value,i=t.currency.value,a=t.unit.value;if(typeof r!=`string`||!Q.test(r))throw TypeError(`Money JSON amount must be a canonical integer string`);if(typeof i!=`string`||a!==`minor`)throw TypeError(`Money JSON currency/unit are invalid`);return{amount:r,currency:i,unit:a}}export{v as BHD,e as CoinsError,t as CurrencyMismatchError,h as EUR,g as GBP,n as InvalidCurrencyError,_ as JPY,ee as KRW,y as KWD,m as USD,ae as abs,te as add,U as allocate,W as clamp,j as compare,x as currency,o as decimal,b as defineCurrency,ie as divide,q as exchange,K as exchangeRate,se as format,Z as formatParts,S as isCurrency,A as isMoney,O as money,re as multiply,M as negate,k as parseMoney,fe as parseMoneyJSON,N as round,ne as subtract,H as sum,P as toDecimal,de as toJSON};
2
2
  //# sourceMappingURL=coins.js.map