@arki/money 0.0.0-stage → 0.0.4

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +238 -2
  3. package/dist/bag.d.ts +48 -0
  4. package/dist/bag.d.ts.map +1 -0
  5. package/dist/bag.js +110 -0
  6. package/dist/bag.js.map +1 -0
  7. package/dist/currency.d.ts +64 -0
  8. package/dist/currency.d.ts.map +1 -0
  9. package/dist/currency.js +148 -0
  10. package/dist/currency.js.map +1 -0
  11. package/dist/data/iso4217.d.ts +9 -0
  12. package/dist/data/iso4217.d.ts.map +1 -0
  13. package/dist/data/iso4217.js +11 -0
  14. package/dist/data/iso4217.js.map +1 -0
  15. package/dist/drizzle/config.d.ts +27 -0
  16. package/dist/drizzle/config.d.ts.map +1 -0
  17. package/dist/drizzle/config.js +16 -0
  18. package/dist/drizzle/config.js.map +1 -0
  19. package/dist/drizzle/mysql.d.ts +36 -0
  20. package/dist/drizzle/mysql.d.ts.map +1 -0
  21. package/dist/drizzle/mysql.js +45 -0
  22. package/dist/drizzle/mysql.js.map +1 -0
  23. package/dist/drizzle/pg.d.ts +38 -0
  24. package/dist/drizzle/pg.d.ts.map +1 -0
  25. package/dist/drizzle/pg.js +49 -0
  26. package/dist/drizzle/pg.js.map +1 -0
  27. package/dist/drizzle/sqlite.d.ts +38 -0
  28. package/dist/drizzle/sqlite.d.ts.map +1 -0
  29. package/dist/drizzle/sqlite.js +45 -0
  30. package/dist/drizzle/sqlite.js.map +1 -0
  31. package/dist/drizzle.d.ts +56 -0
  32. package/dist/drizzle.d.ts.map +1 -0
  33. package/dist/drizzle.js +77 -0
  34. package/dist/drizzle.js.map +1 -0
  35. package/dist/errors.d.ts +32 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +42 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/exact.d.ts +20 -0
  40. package/dist/exact.d.ts.map +1 -0
  41. package/dist/exact.js +23 -0
  42. package/dist/exact.js.map +1 -0
  43. package/dist/exchange.d.ts +84 -0
  44. package/dist/exchange.d.ts.map +1 -0
  45. package/dist/exchange.js +215 -0
  46. package/dist/exchange.js.map +1 -0
  47. package/dist/format.d.ts +41 -0
  48. package/dist/format.d.ts.map +1 -0
  49. package/dist/format.js +122 -0
  50. package/dist/format.js.map +1 -0
  51. package/dist/index.d.ts +7 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +4 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/internal/columns.d.ts +25 -0
  56. package/dist/internal/columns.d.ts.map +1 -0
  57. package/dist/internal/columns.js +47 -0
  58. package/dist/internal/columns.js.map +1 -0
  59. package/dist/internal/units.d.ts +66 -0
  60. package/dist/internal/units.d.ts.map +1 -0
  61. package/dist/internal/units.js +205 -0
  62. package/dist/internal/units.js.map +1 -0
  63. package/dist/money.d.ts +180 -0
  64. package/dist/money.d.ts.map +1 -0
  65. package/dist/money.js +577 -0
  66. package/dist/money.js.map +1 -0
  67. package/dist/parse.d.ts +53 -0
  68. package/dist/parse.d.ts.map +1 -0
  69. package/dist/parse.js +222 -0
  70. package/dist/parse.js.map +1 -0
  71. package/dist/provider.d.ts +22 -0
  72. package/dist/provider.d.ts.map +1 -0
  73. package/dist/provider.js +56 -0
  74. package/dist/provider.js.map +1 -0
  75. package/dist/rational.d.ts +67 -0
  76. package/dist/rational.d.ts.map +1 -0
  77. package/dist/rational.js +169 -0
  78. package/dist/rational.js.map +1 -0
  79. package/dist/stripe.d.ts +37 -0
  80. package/dist/stripe.d.ts.map +1 -0
  81. package/dist/stripe.js +75 -0
  82. package/dist/stripe.js.map +1 -0
  83. package/dist/zod.d.ts +54 -0
  84. package/dist/zod.d.ts.map +1 -0
  85. package/dist/zod.js +96 -0
  86. package/dist/zod.js.map +1 -0
  87. package/package.json +137 -4
  88. package/src/bag.ts +128 -0
  89. package/src/currency.ts +174 -0
  90. package/src/data/iso4217.ts +13 -0
  91. package/src/drizzle/config.ts +39 -0
  92. package/src/drizzle/mysql.ts +61 -0
  93. package/src/drizzle/pg.ts +64 -0
  94. package/src/drizzle/sqlite.ts +61 -0
  95. package/src/errors.ts +61 -0
  96. package/src/exact.ts +29 -0
  97. package/src/exchange.ts +280 -0
  98. package/src/format.ts +148 -0
  99. package/src/index.ts +16 -0
  100. package/src/internal/columns.ts +74 -0
  101. package/src/internal/units.ts +223 -0
  102. package/src/money.ts +692 -0
  103. package/src/parse.ts +278 -0
  104. package/src/provider.ts +67 -0
  105. package/src/rational.ts +214 -0
  106. package/src/stripe.ts +84 -0
  107. package/src/zod.ts +123 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQxD,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,eAAe,CAAC;AAG5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,MAAM,YAAY,GAAG;IACzB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iGAAiG;IACjG,UAAU,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,kEAAkE;IAClE,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI;IAC1C,uGAAuG;IACvG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9B,sGAAsG;IACtG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;CAC9C,CAAC;AAgLF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EACtD,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE,YAAiB,GACzB,WAAW,CAAC,CAAC,CAAC,CA4BhB;AAED,oGAAoG;AACpG,wBAAgB,UAAU,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EAC/C,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,YAAY,GACrB,KAAK,CAAC,CAAC,CAAC,CAEV"}
package/dist/parse.js ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Strict parsing of money typed by people: `'1.234,56 €'` in German,
3
+ * `'$1,234.56'` in English, `'₹1,23,456.78'` in Indian English.
4
+ *
5
+ * The locale's separators, digits, minus sign and currency symbols come from
6
+ * `Intl.NumberFormat#formatToParts`. You always say which currency you expect,
7
+ * so `'$10'` never has to guess between US, Canadian or Australian dollars.
8
+ *
9
+ * Strict means it refuses instead of guessing: grouping in the wrong place
10
+ * (`'1,23,4'`), a decimal separator the locale does not use, two signs, mixed
11
+ * digit scripts, a different currency's symbol, or any trailing text all throw
12
+ * `NumberFormatError`. Text without grouping is always fine: `'1234.5'`.
13
+ *
14
+ * Runtimes without `formatToParts` (React Native's Hermes on iOS) throw
15
+ * `UnsupportedFeatureError`; pass `symbols` to parse without `Intl`.
16
+ */
17
+ import { NumberFormatError, ResourceLimitError, RoundingNecessaryError, UnsupportedFeatureError, } from '@arki/math/errors';
18
+ import { toCurrency } from './currency.js';
19
+ import { MAX_INPUT_LENGTH } from './internal/units.js';
20
+ import { Money } from './money.js';
21
+ const SPACES = /\s/g;
22
+ const BIDI = /[\u061C\u200E\u200F]/g;
23
+ const ASCII_DIGITS = /^\d+$/;
24
+ function normalizeSpaces(text) {
25
+ return text.replaceAll(SPACES, ' ');
26
+ }
27
+ function grammarFromSymbols(currency, symbols) {
28
+ const [primary, secondary] = symbols.groupSizes ?? [3];
29
+ return {
30
+ decimal: normalizeSpaces(symbols.decimal),
31
+ group: symbols.group === undefined ? undefined : normalizeSpaces(symbols.group),
32
+ primary,
33
+ secondary: secondary ?? primary,
34
+ digits: undefined,
35
+ minus: ['-', '\u2212'],
36
+ marks: sortMarks([currency.code, ...(symbols.currency ?? [])]),
37
+ };
38
+ }
39
+ function sortMarks(marks) {
40
+ return [...new Set(marks.map(mark => normalizeSpaces(mark).trim()))]
41
+ .filter(mark => mark !== '')
42
+ .sort((a, b) => b.length - a.length);
43
+ }
44
+ function grammarFromIntl(currency, locale) {
45
+ let plain;
46
+ try {
47
+ plain = new Intl.NumberFormat(locale, { minimumFractionDigits: 1, useGrouping: true });
48
+ }
49
+ catch (error) {
50
+ throw new UnsupportedFeatureError(`This runtime's Intl does not support the locale ${locale}`, { cause: error });
51
+ }
52
+ if (typeof plain.formatToParts !== 'function') {
53
+ throw new UnsupportedFeatureError('This runtime lacks Intl.NumberFormat#formatToParts; pass options.symbols');
54
+ }
55
+ const parts = plain.formatToParts(-1_234_567_890.5);
56
+ const find = (type) => parts.find(part => part.type === type)?.value;
57
+ const integers = parts.filter(part => part.type === 'integer').map(part => part.value.length);
58
+ const primary = integers.at(-1) ?? 3;
59
+ const secondary = integers.length > 2 ? (integers.at(-2) ?? primary) : primary;
60
+ const digitFormat = new Intl.NumberFormat(locale, { useGrouping: false });
61
+ const localDigits = Array.from({ length: 10 }, (_, digit) => digitFormat.format(digit)).join('');
62
+ // The locale's symbols plus the English ones: people in Japan often type `¥` where Intl writes `¥`.
63
+ // Safe, because the caller has already said which currency to expect.
64
+ const marks = [currency.code];
65
+ if (currency.iso) {
66
+ for (const markLocale of [locale, 'en']) {
67
+ for (const currencyDisplay of ['symbol', 'narrowSymbol']) {
68
+ try {
69
+ const formatted = new Intl.NumberFormat(markLocale, {
70
+ style: 'currency',
71
+ currency: currency.code,
72
+ currencyDisplay,
73
+ });
74
+ for (const part of formatted.formatToParts(1))
75
+ if (part.type === 'currency')
76
+ marks.push(part.value);
77
+ }
78
+ catch {
79
+ // A display the runtime lacks simply adds no mark; the ISO code is always accepted.
80
+ }
81
+ }
82
+ }
83
+ }
84
+ const group = find('group');
85
+ return {
86
+ decimal: normalizeSpaces(find('decimal') ?? '.'),
87
+ group: group === undefined ? undefined : normalizeSpaces(group),
88
+ primary,
89
+ secondary,
90
+ digits: localDigits === '0123456789' ? undefined : localDigits,
91
+ minus: [...new Set([find('minusSign') ?? '-', '-', '\u2212'])],
92
+ marks: sortMarks(marks),
93
+ };
94
+ }
95
+ function fail(text, reason) {
96
+ throw new NumberFormatError(`Cannot read ${JSON.stringify(text.slice(0, 40))} as money: ${reason}`);
97
+ }
98
+ /** Turns locale text into plain `'-1234.56'`, or throws `NumberFormatError`. */
99
+ function toPlainDecimal(input, grammar) {
100
+ if (input.length > MAX_INPUT_LENGTH) {
101
+ throw new ResourceLimitError(`Money text is longer than ${MAX_INPUT_LENGTH} characters`);
102
+ }
103
+ let text = normalizeSpaces(input.replaceAll(BIDI, '')).trim();
104
+ let negative = false;
105
+ if (text.startsWith('(') && text.endsWith(')')) {
106
+ negative = true;
107
+ text = text.slice(1, -1).trim();
108
+ }
109
+ // Leading sign and currency mark, in either order; a trailing currency mark.
110
+ let sawSign = false;
111
+ let sawMark = false;
112
+ for (let round = 0; round < 2; round++) {
113
+ const sign = [...grammar.minus, '+'].find(candidate => text.startsWith(candidate));
114
+ if (sign !== undefined && !sawSign) {
115
+ sawSign = true;
116
+ if (sign !== '+')
117
+ negative = !negative;
118
+ text = text.slice(sign.length).trim();
119
+ }
120
+ const mark = grammar.marks.find(candidate => text.startsWith(candidate));
121
+ if (mark !== undefined && !sawMark) {
122
+ sawMark = true;
123
+ text = text.slice(mark.length).trim();
124
+ }
125
+ }
126
+ const trailing = grammar.marks.find(candidate => text.endsWith(candidate));
127
+ if (trailing !== undefined) {
128
+ if (sawMark)
129
+ fail(input, 'the currency appears twice');
130
+ text = text.slice(0, -trailing.length).trim();
131
+ }
132
+ if (sawSign && negative && input.trim().startsWith('('))
133
+ fail(input, 'it has both parentheses and a minus sign');
134
+ // Local digits → ASCII, refusing a mix of scripts.
135
+ if (grammar.digits !== undefined) {
136
+ let local = false;
137
+ let ascii = false;
138
+ let converted = '';
139
+ for (const char of text) {
140
+ const index = grammar.digits.indexOf(char);
141
+ if (index === -1) {
142
+ if (char >= '0' && char <= '9')
143
+ ascii = true;
144
+ converted += char;
145
+ }
146
+ else {
147
+ local = true;
148
+ converted += String(index);
149
+ }
150
+ }
151
+ if (local && ascii)
152
+ fail(input, 'it mixes digit scripts');
153
+ text = converted;
154
+ }
155
+ const pieces = text.split(grammar.decimal);
156
+ if (pieces.length > 2)
157
+ fail(input, `the decimal separator ${JSON.stringify(grammar.decimal)} appears twice`);
158
+ const [whole = '', fraction] = pieces;
159
+ if (fraction !== undefined && !ASCII_DIGITS.test(fraction))
160
+ fail(input, 'the decimals are not digits');
161
+ const group = grammar.group;
162
+ if (!ASCII_DIGITS.test(group === undefined ? whole : whole.replaceAll(group, ''))) {
163
+ fail(input, 'it is not a number in this locale');
164
+ }
165
+ let digits = whole;
166
+ if (group !== undefined && whole.includes(group)) {
167
+ const groups = whole.split(group);
168
+ const last = groups.length - 1;
169
+ const valid = groups.every((group, index) => {
170
+ if (!ASCII_DIGITS.test(group))
171
+ return false;
172
+ if (index === last)
173
+ return group.length === grammar.primary;
174
+ if (index === 0)
175
+ return group.length <= (last === 1 ? grammar.primary : grammar.secondary);
176
+ return group.length === grammar.secondary;
177
+ });
178
+ if (!valid)
179
+ fail(input, 'the digit grouping does not match the locale');
180
+ digits = groups.join('');
181
+ }
182
+ if (!ASCII_DIGITS.test(digits))
183
+ fail(input, 'it is not a number in this locale');
184
+ return `${negative ? '-' : ''}${digits}${fraction === undefined ? '' : `.${fraction}`}`;
185
+ }
186
+ /**
187
+ * Prepares the locale's grammar once, for forms and imports:
188
+ * `const parse = createMoneyParser('EUR', { locale: 'de-DE' }); parse.parse('1.234,56 €')`.
189
+ */
190
+ export function createMoneyParser(currency, options = {}) {
191
+ const target = toCurrency(currency);
192
+ const grammar = options.symbols === undefined
193
+ ? grammarFromIntl(target, options.locale ?? 'en-US')
194
+ : grammarFromSymbols(target, options.symbols);
195
+ const settle = { rounding: options.rounding, scale: options.scale };
196
+ const parse = (text) => {
197
+ if (typeof text !== 'string')
198
+ throw new TypeError('Expected text to parse');
199
+ return Money.of(toPlainDecimal(text, grammar), target, settle);
200
+ };
201
+ return {
202
+ parse,
203
+ tryParse(text) {
204
+ try {
205
+ return parse(text);
206
+ }
207
+ catch (error) {
208
+ if (error instanceof NumberFormatError ||
209
+ error instanceof RoundingNecessaryError ||
210
+ error instanceof ResourceLimitError) {
211
+ return;
212
+ }
213
+ throw error;
214
+ }
215
+ },
216
+ };
217
+ }
218
+ /** One-shot form of `createMoneyParser`: `parseMoney('1.234,56 €', 'EUR', { locale: 'de-DE' })`. */
219
+ export function parseMoney(text, currency, options) {
220
+ return createMoneyParser(currency, options).parse(text);
221
+ }
222
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Payment providers count amounts in their own units, which are not always
3
+ * ISO's. A rule says how many decimals the provider's integer carries and,
4
+ * optionally, which multiple it must be. Conversion never rounds: an amount
5
+ * the provider cannot express throws `ProviderAmountError`.
6
+ *
7
+ * `@arki/money/stripe` ships Stripe's rules. Describe any other provider with
8
+ * a `ProviderUnitRule` and these two functions.
9
+ */
10
+ import type { CurrencyLike } from './currency.js';
11
+ import { Money } from './money.js';
12
+ export type ProviderUnitRule = {
13
+ /** Decimals in the provider's integer: 2 means `1099` is `10.99`. */
14
+ scale: number;
15
+ /** The integer must be a multiple of this: `100` for whole-unit-only currencies sent with two decimals. */
16
+ step?: number;
17
+ };
18
+ /** `toProviderUnits(money('10.99', 'USD'), { scale: 2 })` → `1099`. Throws instead of rounding. */
19
+ export declare function toProviderUnits(amount: Money, rule: ProviderUnitRule): number;
20
+ /** The reverse of `toProviderUnits`, at the currency's own scale. Validates the step. */
21
+ export declare function fromProviderUnits<const C extends string>(units: number | bigint | string, currency: CurrencyLike<C>, rule: ProviderUnitRule): Money<C>;
22
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAKlD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,2GAA2G;IAC3G,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAQF,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAa7E;AAED,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EACtD,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAC/B,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EACzB,IAAI,EAAE,gBAAgB,GACrB,KAAK,CAAC,CAAC,CAAC,CAaV"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Payment providers count amounts in their own units, which are not always
3
+ * ISO's. A rule says how many decimals the provider's integer carries and,
4
+ * optionally, which multiple it must be. Conversion never rounds: an amount
5
+ * the provider cannot express throws `ProviderAmountError`.
6
+ *
7
+ * `@arki/money/stripe` ships Stripe's rules. Describe any other provider with
8
+ * a `ProviderUnitRule` and these two functions.
9
+ */
10
+ import { NumericRangeError, RoundingNecessaryError } from '@arki/math/errors';
11
+ import { toCurrency } from './currency.js';
12
+ import { ProviderAmountError } from './errors.js';
13
+ import { quantize } from './internal/units.js';
14
+ import { Money } from './money.js';
15
+ function checkStep(units, rule, what) {
16
+ if (rule.step !== undefined && BigInt(units) % BigInt(rule.step) !== 0n) {
17
+ throw new ProviderAmountError(`${what} must be a multiple of ${rule.step} in the provider's units`);
18
+ }
19
+ }
20
+ /** `toProviderUnits(money('10.99', 'USD'), { scale: 2 })` → `1099`. Throws instead of rounding. */
21
+ export function toProviderUnits(amount, rule) {
22
+ let units;
23
+ try {
24
+ units = quantize(amount.units, amount.scale, rule.scale);
25
+ }
26
+ catch (error) {
27
+ if (!(error instanceof RoundingNecessaryError))
28
+ throw error;
29
+ throw new ProviderAmountError(`${amount.toString()} has more decimals than the provider accepts (${rule.scale})`, {
30
+ cause: error,
31
+ });
32
+ }
33
+ checkStep(units, rule, amount.toString());
34
+ if (typeof units !== 'number')
35
+ throw new NumericRangeError(`${amount.toString()} is too large for a provider amount`);
36
+ return units;
37
+ }
38
+ /** The reverse of `toProviderUnits`, at the currency's own scale. Validates the step. */
39
+ export function fromProviderUnits(units, currency, rule) {
40
+ const target = toCurrency(currency);
41
+ const raw = Money.fromMinor(units, target, rule.scale);
42
+ checkStep(raw.units, rule, `The provider amount ${String(units)}`);
43
+ if (rule.scale <= target.scale)
44
+ return raw.toScale(target.scale);
45
+ try {
46
+ return raw.toScale(target.scale);
47
+ }
48
+ catch (error) {
49
+ if (!(error instanceof RoundingNecessaryError))
50
+ throw error;
51
+ throw new ProviderAmountError(`The provider amount ${String(units)} has a fraction ${target.code} does not have`, {
52
+ cause: error,
53
+ });
54
+ }
55
+ }
56
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAI9E,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AASnC,SAAS,SAAS,CAAC,KAAY,EAAE,IAAsB,EAAE,IAAY;IACnE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QACxE,MAAM,IAAI,mBAAmB,CAAC,GAAG,IAAI,0BAA0B,IAAI,CAAC,IAAI,0BAA0B,CAAC,CAAC;IACtG,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,eAAe,CAAC,MAAa,EAAE,IAAsB;IACnE,IAAI,KAAY,CAAC;IACjB,IAAI,CAAC;QACH,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,sBAAsB,CAAC;YAAE,MAAM,KAAK,CAAC;QAC5D,MAAM,IAAI,mBAAmB,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,iDAAiD,IAAI,CAAC,KAAK,GAAG,EAAE;YAChH,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IACD,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,iBAAiB,CAAC,GAAG,MAAM,CAAC,QAAQ,EAAE,qCAAqC,CAAC,CAAC;IACtH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,iBAAiB,CAC/B,KAA+B,EAC/B,QAAyB,EACzB,IAAsB;IAEtB,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,uBAAuB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACnE,IAAI,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,sBAAsB,CAAC;YAAE,MAAM,KAAK,CAAC;QAC5D,MAAM,IAAI,mBAAmB,CAAC,uBAAuB,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,CAAC,IAAI,gBAAgB,EAAE;YAChH,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Exact in-between money. A `RationalMoney` is a fraction of major units plus
3
+ * a currency, so a chain of steps (split a third, add tax, convert) never
4
+ * rounds until you settle it with `toMoney`, and the currency travels with
5
+ * the value the whole way: EUR and USD still cannot be mixed.
6
+ *
7
+ * ```ts
8
+ * const share = RationalMoney.from(money(100, 'EUR')).dividedBy(3).times('1.19');
9
+ * share.toString(); // '119/3 EUR'
10
+ * share.toMoney({ rounding: 'halfEven' }); // 39.67 EUR, rounded once
11
+ * ```
12
+ */
13
+ import type { Rational } from '@arki/math/rational';
14
+ import type { Currency, CurrencyLike } from './currency.js';
15
+ import type { Amount, FactorInput, MoneyJSON, MoneyOptions } from './money.js';
16
+ import { Money } from './money.js';
17
+ /** Something with an exact value in a currency: a `Money` or a `RationalMoney`. */
18
+ export type ExactMoney<C extends string = string> = Money<C> | RationalMoney<C>;
19
+ /** An immutable exact amount: a fraction of major units in one currency. */
20
+ export declare class RationalMoney<C extends string = string> {
21
+ /** Major units as a reduced fraction. */
22
+ readonly amount: Rational;
23
+ readonly currency: Currency<C>;
24
+ private constructor();
25
+ /** `RationalMoney.of('1/3', 'EUR')`, or any `Amount` (`'19.99'`, `ratio(1, 3)`). Never rounds. */
26
+ static of<const C extends string>(amount: Amount | ExactMoney, currency: CurrencyLike<C>): RationalMoney<C>;
27
+ /** The exact value of a `Money`. */
28
+ static from<C extends string>(value: Money<C>): RationalMoney<C>;
29
+ /** Zero in this currency. */
30
+ static zero<const C extends string>(currency: CurrencyLike<C>): RationalMoney<C>;
31
+ /** Reads `{ amount: '1/3', currency: 'EUR' }`, as written by `toJSON`. */
32
+ static fromJSON(value: unknown, currencies?: readonly Currency[]): RationalMoney;
33
+ plus(other: ExactMoney<C>): RationalMoney<C>;
34
+ minus(other: ExactMoney<C>): RationalMoney<C>;
35
+ times(multiplier: FactorInput): RationalMoney<C>;
36
+ /** Zero throws `DivisionByZeroError`. */
37
+ dividedBy(divisor: FactorInput): RationalMoney<C>;
38
+ /** `percentage(15)` is `times(15 / 100)`. */
39
+ percentage(percent: FactorInput): RationalMoney<C>;
40
+ negated(): RationalMoney<C>;
41
+ abs(): RationalMoney<C>;
42
+ compareTo(other: ExactMoney<C>): -1 | 0 | 1;
43
+ eq(other: ExactMoney<C>): boolean;
44
+ lt(other: ExactMoney<C>): boolean;
45
+ lte(other: ExactMoney<C>): boolean;
46
+ gt(other: ExactMoney<C>): boolean;
47
+ gte(other: ExactMoney<C>): boolean;
48
+ isZero(): boolean;
49
+ isPositive(): boolean;
50
+ isNegative(): boolean;
51
+ sign(): -1 | 0 | 1;
52
+ /**
53
+ * Rounds once to a `Money` at the currency's scale (or `options.scale`).
54
+ * An inexact value needs `options.rounding`: `'1/3 EUR'` throws without it.
55
+ */
56
+ toMoney(options?: MoneyOptions): Money<C>;
57
+ /** `'1/3 EUR'`, `'5 EUR'`. */
58
+ toString(): string;
59
+ /** `{ amount: '1/3', currency: 'EUR' }`. */
60
+ toJSON(): MoneyJSON;
61
+ /** Throws. Settle with `toMoney()` first. */
62
+ valueOf(): never;
63
+ [Symbol.toPrimitive](hint: string): string;
64
+ get [Symbol.toStringTag](): string;
65
+ }
66
+ export declare function isRationalMoney(value: unknown): value is RationalMoney;
67
+ //# sourceMappingURL=rational.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rational.d.ts","sourceRoot":"","sources":["../src/rational.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Exact in-between money. A `RationalMoney` is a fraction of major units plus
3
+ * a currency, so a chain of steps (split a third, add tax, convert) never
4
+ * rounds until you settle it with `toMoney`, and the currency travels with
5
+ * the value the whole way: EUR and USD still cannot be mixed.
6
+ *
7
+ * ```ts
8
+ * const share = RationalMoney.from(money(100, 'EUR')).dividedBy(3).times('1.19');
9
+ * share.toString(); // '119/3 EUR'
10
+ * share.toMoney({ rounding: 'halfEven' }); // 39.67 EUR, rounded once
11
+ * ```
12
+ */
13
+ import { InvalidArgumentError } from '@arki/math/errors';
14
+ import { ratio } from '@arki/math/rational';
15
+ import { assertSameCurrency, resolveCurrency, toCurrency } from './currency.js';
16
+ import { Money, toFraction } from './money.js';
17
+ const BRAND = Symbol('RationalMoney');
18
+ function fraction(value) {
19
+ const [numerator, denominator] = toFraction(value);
20
+ return ratio(BigInt(numerator), BigInt(denominator));
21
+ }
22
+ function toRationalAmount(value) {
23
+ return value instanceof RationalMoney ? value.amount : ratio(BigInt(value.units), 10n ** BigInt(value.scale));
24
+ }
25
+ function refuseCoercion() {
26
+ throw new TypeError('RationalMoney cannot be turned into a number implicitly; settle it with toMoney()');
27
+ }
28
+ /** An immutable exact amount: a fraction of major units in one currency. */
29
+ export class RationalMoney {
30
+ /** Major units as a reduced fraction. */
31
+ amount;
32
+ currency;
33
+ constructor(amount, currency, brand) {
34
+ if (brand !== BRAND)
35
+ throw new TypeError('Use RationalMoney.of() or RationalMoney.from() instead of new');
36
+ this.amount = amount;
37
+ this.currency = currency;
38
+ }
39
+ /** `RationalMoney.of('1/3', 'EUR')`, or any `Amount` (`'19.99'`, `ratio(1, 3)`). Never rounds. */
40
+ static of(amount, currency) {
41
+ const cur = toCurrency(currency);
42
+ if (amount instanceof Money || amount instanceof RationalMoney) {
43
+ assertSameCurrency(amount.currency, cur);
44
+ return new RationalMoney(toRationalAmount(amount), cur, BRAND);
45
+ }
46
+ if (typeof amount === 'string' && amount.includes('/'))
47
+ return new RationalMoney(ratio(amount), cur, BRAND);
48
+ return new RationalMoney(fraction(amount), cur, BRAND);
49
+ }
50
+ /** The exact value of a `Money`. */
51
+ static from(value) {
52
+ return new RationalMoney(toRationalAmount(value), value.currency, BRAND);
53
+ }
54
+ /** Zero in this currency. */
55
+ static zero(currency) {
56
+ return new RationalMoney(ratio(0), toCurrency(currency), BRAND);
57
+ }
58
+ /** Reads `{ amount: '1/3', currency: 'EUR' }`, as written by `toJSON`. */
59
+ static fromJSON(value, currencies) {
60
+ if (typeof value !== 'object' || value === null || !('amount' in value) || !('currency' in value)) {
61
+ throw new InvalidArgumentError('Expected { amount: string, currency: string }');
62
+ }
63
+ const { amount, currency } = value;
64
+ if (typeof amount !== 'string' || typeof currency !== 'string') {
65
+ throw new InvalidArgumentError('Expected { amount: string, currency: string }');
66
+ }
67
+ return RationalMoney.of(amount, resolveCurrency(currency, currencies));
68
+ }
69
+ /** @internal */
70
+ static create(amount, currency) {
71
+ return new RationalMoney(amount, currency, BRAND);
72
+ }
73
+ // Arithmetic: always exact ------------------------------------------------
74
+ plus(other) {
75
+ assertSameCurrency(this.currency, other.currency);
76
+ return new RationalMoney(this.amount.plus(toRationalAmount(other)), this.currency, BRAND);
77
+ }
78
+ minus(other) {
79
+ assertSameCurrency(this.currency, other.currency);
80
+ return new RationalMoney(this.amount.minus(toRationalAmount(other)), this.currency, BRAND);
81
+ }
82
+ times(multiplier) {
83
+ return new RationalMoney(this.amount.times(fraction(multiplier)), this.currency, BRAND);
84
+ }
85
+ /** Zero throws `DivisionByZeroError`. */
86
+ dividedBy(divisor) {
87
+ return new RationalMoney(this.amount.dividedBy(fraction(divisor)), this.currency, BRAND);
88
+ }
89
+ /** `percentage(15)` is `times(15 / 100)`. */
90
+ percentage(percent) {
91
+ return new RationalMoney(this.amount.times(fraction(percent)).dividedBy(100), this.currency, BRAND);
92
+ }
93
+ negated() {
94
+ return new RationalMoney(this.amount.negated(), this.currency, BRAND);
95
+ }
96
+ abs() {
97
+ return this.amount.isNegative() ? this.negated() : this;
98
+ }
99
+ // Comparison --------------------------------------------------------------
100
+ compareTo(other) {
101
+ assertSameCurrency(this.currency, other.currency);
102
+ return this.amount.compareTo(toRationalAmount(other));
103
+ }
104
+ eq(other) {
105
+ return this.compareTo(other) === 0;
106
+ }
107
+ lt(other) {
108
+ return this.compareTo(other) < 0;
109
+ }
110
+ lte(other) {
111
+ return this.compareTo(other) <= 0;
112
+ }
113
+ gt(other) {
114
+ return this.compareTo(other) > 0;
115
+ }
116
+ gte(other) {
117
+ return this.compareTo(other) >= 0;
118
+ }
119
+ isZero() {
120
+ return this.amount.isZero();
121
+ }
122
+ isPositive() {
123
+ return this.amount.isPositive();
124
+ }
125
+ isNegative() {
126
+ return this.amount.isNegative();
127
+ }
128
+ sign() {
129
+ return this.amount.sign();
130
+ }
131
+ // Settling and output -----------------------------------------------------
132
+ /**
133
+ * Rounds once to a `Money` at the currency's scale (or `options.scale`).
134
+ * An inexact value needs `options.rounding`: `'1/3 EUR'` throws without it.
135
+ */
136
+ toMoney(options) {
137
+ return Money.of(this.amount, this.currency, options);
138
+ }
139
+ /** `'1/3 EUR'`, `'5 EUR'`. */
140
+ toString() {
141
+ return `${this.amount.toString()} ${this.currency.code}`;
142
+ }
143
+ /** `{ amount: '1/3', currency: 'EUR' }`. */
144
+ toJSON() {
145
+ return { amount: this.amount.toString(), currency: this.currency.code };
146
+ }
147
+ /** Throws. Settle with `toMoney()` first. */
148
+ valueOf() {
149
+ return refuseCoercion();
150
+ }
151
+ [Symbol.toPrimitive](hint) {
152
+ return hint === 'string' ? this.toString() : refuseCoercion();
153
+ }
154
+ // A getter lives on the prototype; a field would add a slot to every value.
155
+ // eslint-disable-next-line @typescript-eslint/class-literal-property-style
156
+ get [Symbol.toStringTag]() {
157
+ return 'RationalMoney';
158
+ }
159
+ [Symbol.for('nodejs.util.inspect.custom')]() {
160
+ return `RationalMoney(${this.toString()})`;
161
+ }
162
+ [Symbol.for('Deno.customInspect')]() {
163
+ return `RationalMoney(${this.toString()})`;
164
+ }
165
+ }
166
+ export function isRationalMoney(value) {
167
+ return value instanceof RationalMoney;
168
+ }
169
+ //# sourceMappingURL=rational.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rational.js","sourceRoot":"","sources":["../src/rational.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Stripe amounts. Stripe sends integers in its own units, which differ from
3
+ * ISO 4217 for some currencies. Rules from docs.stripe.com/currencies, read
4
+ * on 2026-10-12:
5
+ *
6
+ * - Zero-decimal: BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF VND VUV XAF XOF XPF
7
+ * (`500` is 500 JPY). MGA has 2 decimals in ISO but 0 at Stripe.
8
+ * - ISK and UGX: sent with two decimals that are always `00` (`500` is 5 ISK).
9
+ * - HUF and TWD: two-decimal charges; payouts must be divisible by 100.
10
+ * - Other currencies with 2 ISO decimals: two decimals.
11
+ *
12
+ * Stripe documents no rule for currencies with 3 or 4 ISO decimals, so those
13
+ * throw `ProviderAmountError` instead of being guessed. Whether your account
14
+ * can charge a currency, and its minimum and maximum amounts, are Stripe's to
15
+ * enforce; this module only gets the units right.
16
+ */
17
+ import type { Currency, CurrencyLike } from './currency.js';
18
+ import type { Money } from './money.js';
19
+ import type { ProviderUnitRule } from './provider.js';
20
+ /** When Stripe's rules in this module were last checked. */
21
+ export declare const STRIPE_RULES_VERSION = "2026-10-12";
22
+ export type StripeOperation = 'charge' | 'payout';
23
+ /** Stripe's unit rule for a currency and operation. Throws `ProviderAmountError` where Stripe documents none. */
24
+ export declare function stripeRule(currency: CurrencyLike, operation?: StripeOperation): ProviderUnitRule;
25
+ /**
26
+ * `toStripeAmount(money('10.99', 'USD'))` → `1099`; `money(5, 'ISK')` → `500`.
27
+ * Throws `ProviderAmountError` for a negative amount or one Stripe cannot express (`10.50 MGA`).
28
+ */
29
+ export declare function toStripeAmount(amount: Money, operation?: StripeOperation): number;
30
+ /** `fromStripeAmount(1099, 'usd')` → `10.99 USD`. Accepts Stripe's lower-case codes. */
31
+ export declare function fromStripeAmount(amount: number | bigint | string, currency: string | Currency, operation?: StripeOperation): Money;
32
+ /** The `{ amount, currency }` pair a Stripe request takes: `{ amount: 1099, currency: 'usd' }`. */
33
+ export declare function toStripe(amount: Money, operation?: StripeOperation): {
34
+ amount: number;
35
+ currency: string;
36
+ };
37
+ //# sourceMappingURL=stripe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stripe.d.ts","sourceRoot":"","sources":["../src/stripe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAKtD,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAsBlD,iHAAiH;AACjH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,GAAE,eAA0B,GAAG,gBAAgB,CAS1G;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,GAAE,eAA0B,GAAG,MAAM,CAG3F;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAChC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAC3B,SAAS,GAAE,eAA0B,GACpC,KAAK,CAGP;AAED,mGAAmG;AACnG,wBAAgB,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,GAAE,eAA0B,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEnH"}