@ultimat3/money 21.0.0 → 22.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,6 +137,15 @@ the epoch: `ExchangeRate.at` is the audit trail.
137
137
  | `X_ALLOCATION_INVALID` | bad part count, empty/negative/all-zero ratios, percentages ≠ 100 |
138
138
  | `X_RATE_MISSING` | no rate for the pair — never assumes parity |
139
139
 
140
+ ### Error classes
141
+
142
+ Every error class `src/index.ts` exports, for `instanceof` inside one process. Across a wire or
143
+ a job boundary the class is gone and the `code` is what survives — match on that.
144
+
145
+ | Class | Code | Declared in |
146
+ |---|---|---|
147
+ | `MoneyError` | any `MoneyErrorCode` — `MONEY_ERROR_CODES` | `src/errors.ts` |
148
+
140
149
  ## Why it exists
141
150
 
142
151
  Every money bug in production is one of three things: a float, a missing currency, or a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/money",
3
- "version": "21.0.0",
3
+ "version": "22.0.0",
4
4
  "description": "Integer minor units with an attached currency: arithmetic, allocation, rounding, Intl formatting",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "test": "bun test"
35
35
  },
36
36
  "dependencies": {
37
- "@ultimat3/core": "21.0.0",
38
- "@ultimat3/schema": "21.0.0"
37
+ "@ultimat3/core": "22.0.0",
38
+ "@ultimat3/schema": "22.0.0"
39
39
  }
40
40
  }
package/src/format.ts CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { assertLocale, cachedFormatter } from '@ultimat3/core';
7
7
  import { exponentOf } from './currency';
8
- import { type Money, toDecimalNumber } from './money';
8
+ import { type Money, toDecimalString } from './money';
9
9
  import { moneyScale } from './scale';
10
10
 
11
11
  export interface FormatMoneyOptions {
@@ -56,7 +56,7 @@ export function formatMoneyParts(
56
56
  options: FormatMoneyOptions = {},
57
57
  ): Intl.NumberFormatPart[] {
58
58
  return formatterFor(amount.currency, locale, options, moneyScale(amount)).formatToParts(
59
- toDecimalNumber(amount),
59
+ exactDecimal(amount),
60
60
  );
61
61
  }
62
62
 
@@ -87,9 +87,17 @@ export function formatMoneyDecimal(amount: Money, locale: string): string {
87
87
  maximumFractionDigits: digits,
88
88
  useGrouping: false,
89
89
  }),
90
- ).format(toDecimalNumber(amount));
90
+ ).format(exactDecimal(amount));
91
91
  }
92
92
 
93
+ /**
94
+ * The amount as the decimal STRING `Intl` accepts (Intl.NumberFormat v3), never a float: dividing
95
+ * `9007199254740991` minor units by `10 ** 6` in floating point lands on `…740992` before any
96
+ * formatter sees it, so the largest amount a `Money` can hold rendered a digit it does not have.
97
+ * The cast is the one `toDecimalString` earns: it only ever writes `-?\d+(\.\d+)?`.
98
+ */
99
+ const exactDecimal = (amount: Money): `${number}` => toDecimalString(amount) as `${number}`;
100
+
93
101
  const cache = new Map<string, Intl.NumberFormat>();
94
102
  const decimalCache = new Map<string, Intl.NumberFormat>();
95
103
 
package/src/money.ts CHANGED
@@ -112,8 +112,10 @@ export function toDecimalString(amount: Money): string {
112
112
  }
113
113
 
114
114
  /**
115
- * Major units as a float. **Format-time only** — the one place a division by the
116
- * currency scale is legitimate, because `Intl.NumberFormat` takes a number.
115
+ * Major units as a float — lossy past 2^53 / 10^scale, so never for arithmetic and no longer for
116
+ * formatting either: `format.ts` hands `Intl` the exact `toDecimalString`, since a float rendered
117
+ * `money(9007199254740991, 'USD', 6)` as `…740992`. Kept as public API for a chart axis or a sort
118
+ * key, where an approximation is the point.
117
119
  */
118
120
  export function toDecimalNumber(amount: Money): number {
119
121
  return amount.minor / 10 ** moneyScale(amount);