@ducatjs/money 0.0.0 → 0.2.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 (398) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +51 -0
  3. package/README.md +1034 -3
  4. package/assets/CREDITS.md +12 -0
  5. package/data/adyen-fx.json +19 -0
  6. package/data/adyen.json +171 -0
  7. package/data/braintree.json +223 -0
  8. package/data/cash-rounding.json +520 -0
  9. package/data/checkout.json +230 -0
  10. package/data/cldr-cash.json +101 -0
  11. package/data/historic.json +78 -0
  12. package/data/iso4217-list-one.xml +1956 -0
  13. package/data/iso4217-list-three.xml +1185 -0
  14. package/data/klarna.json +95 -0
  15. package/data/paypal.json +65 -0
  16. package/data/stripe-fx.json +17 -0
  17. package/data/stripe.json +285 -0
  18. package/dist/adyen-fx.d.ts +25 -0
  19. package/dist/adyen-fx.d.ts.map +1 -0
  20. package/dist/adyen-fx.js +140 -0
  21. package/dist/adyen-fx.js.map +1 -0
  22. package/dist/adyen.d.ts +25 -0
  23. package/dist/adyen.d.ts.map +1 -0
  24. package/dist/adyen.js +34 -0
  25. package/dist/adyen.js.map +1 -0
  26. package/dist/allocate.d.ts +72 -0
  27. package/dist/allocate.d.ts.map +1 -0
  28. package/dist/allocate.js +314 -0
  29. package/dist/allocate.js.map +1 -0
  30. package/dist/bag.d.ts +58 -0
  31. package/dist/bag.d.ts.map +1 -0
  32. package/dist/bag.js +122 -0
  33. package/dist/bag.js.map +1 -0
  34. package/dist/boc.d.ts +31 -0
  35. package/dist/boc.d.ts.map +1 -0
  36. package/dist/boc.js +114 -0
  37. package/dist/boc.js.map +1 -0
  38. package/dist/boe.d.ts +33 -0
  39. package/dist/boe.d.ts.map +1 -0
  40. package/dist/boe.js +114 -0
  41. package/dist/boe.js.map +1 -0
  42. package/dist/braintree.d.ts +34 -0
  43. package/dist/braintree.d.ts.map +1 -0
  44. package/dist/braintree.js +45 -0
  45. package/dist/braintree.js.map +1 -0
  46. package/dist/cash.d.ts +64 -0
  47. package/dist/cash.d.ts.map +1 -0
  48. package/dist/cash.js +134 -0
  49. package/dist/cash.js.map +1 -0
  50. package/dist/checkout.d.ts +42 -0
  51. package/dist/checkout.d.ts.map +1 -0
  52. package/dist/checkout.js +52 -0
  53. package/dist/checkout.js.map +1 -0
  54. package/dist/convert.d.ts +52 -0
  55. package/dist/convert.d.ts.map +1 -0
  56. package/dist/convert.js +65 -0
  57. package/dist/convert.js.map +1 -0
  58. package/dist/currency.d.ts +22 -0
  59. package/dist/currency.d.ts.map +1 -0
  60. package/dist/currency.js +38 -0
  61. package/dist/currency.js.map +1 -0
  62. package/dist/decimal.d.ts +52 -0
  63. package/dist/decimal.d.ts.map +1 -0
  64. package/dist/decimal.js +251 -0
  65. package/dist/decimal.js.map +1 -0
  66. package/dist/drizzle/mysql.d.ts +105 -0
  67. package/dist/drizzle/mysql.d.ts.map +1 -0
  68. package/dist/drizzle/mysql.js +60 -0
  69. package/dist/drizzle/mysql.js.map +1 -0
  70. package/dist/drizzle/pg.d.ts +103 -0
  71. package/dist/drizzle/pg.d.ts.map +1 -0
  72. package/dist/drizzle/pg.js +60 -0
  73. package/dist/drizzle/pg.js.map +1 -0
  74. package/dist/drizzle/sqlite.d.ts +102 -0
  75. package/dist/drizzle/sqlite.d.ts.map +1 -0
  76. package/dist/drizzle/sqlite.js +59 -0
  77. package/dist/drizzle/sqlite.js.map +1 -0
  78. package/dist/ecb.d.ts +52 -0
  79. package/dist/ecb.d.ts.map +1 -0
  80. package/dist/ecb.js +285 -0
  81. package/dist/ecb.js.map +1 -0
  82. package/dist/exact.d.ts +62 -0
  83. package/dist/exact.d.ts.map +1 -0
  84. package/dist/exact.js +147 -0
  85. package/dist/exact.js.map +1 -0
  86. package/dist/fed.d.ts +35 -0
  87. package/dist/fed.d.ts.map +1 -0
  88. package/dist/fed.js +134 -0
  89. package/dist/fed.js.map +1 -0
  90. package/dist/fetch.d.ts +103 -0
  91. package/dist/fetch.d.ts.map +1 -0
  92. package/dist/fetch.js +222 -0
  93. package/dist/fetch.js.map +1 -0
  94. package/dist/fixed.d.ts +121 -0
  95. package/dist/fixed.d.ts.map +1 -0
  96. package/dist/fixed.js +420 -0
  97. package/dist/fixed.js.map +1 -0
  98. package/dist/fn.d.ts +34 -0
  99. package/dist/fn.d.ts.map +1 -0
  100. package/dist/fn.js +37 -0
  101. package/dist/fn.js.map +1 -0
  102. package/dist/format.d.ts +13 -0
  103. package/dist/format.d.ts.map +1 -0
  104. package/dist/format.js +90 -0
  105. package/dist/format.js.map +1 -0
  106. package/dist/generated/adyen.d.ts +3 -0
  107. package/dist/generated/adyen.d.ts.map +1 -0
  108. package/dist/generated/adyen.js +144 -0
  109. package/dist/generated/adyen.js.map +1 -0
  110. package/dist/generated/braintree.d.ts +3 -0
  111. package/dist/generated/braintree.d.ts.map +1 -0
  112. package/dist/generated/braintree.js +140 -0
  113. package/dist/generated/braintree.js.map +1 -0
  114. package/dist/generated/cash.d.ts +14 -0
  115. package/dist/generated/cash.d.ts.map +1 -0
  116. package/dist/generated/cash.js +465 -0
  117. package/dist/generated/cash.js.map +1 -0
  118. package/dist/generated/checkout.d.ts +3 -0
  119. package/dist/generated/checkout.d.ts.map +1 -0
  120. package/dist/generated/checkout.js +151 -0
  121. package/dist/generated/checkout.js.map +1 -0
  122. package/dist/generated/historic.d.ts +337 -0
  123. package/dist/generated/historic.d.ts.map +1 -0
  124. package/dist/generated/historic.js +66 -0
  125. package/dist/generated/historic.js.map +1 -0
  126. package/dist/generated/iso4217.d.ts +174 -0
  127. package/dist/generated/iso4217.d.ts.map +1 -0
  128. package/dist/generated/iso4217.js +175 -0
  129. package/dist/generated/iso4217.js.map +1 -0
  130. package/dist/generated/kernel.d.ts +17 -0
  131. package/dist/generated/kernel.d.ts.map +1 -0
  132. package/dist/generated/kernel.js +208 -0
  133. package/dist/generated/kernel.js.map +1 -0
  134. package/dist/generated/klarna.d.ts +3 -0
  135. package/dist/generated/klarna.d.ts.map +1 -0
  136. package/dist/generated/klarna.js +21 -0
  137. package/dist/generated/klarna.js.map +1 -0
  138. package/dist/generated/paypal.d.ts +3 -0
  139. package/dist/generated/paypal.d.ts.map +1 -0
  140. package/dist/generated/paypal.js +31 -0
  141. package/dist/generated/paypal.js.map +1 -0
  142. package/dist/generated/stripe.d.ts +8 -0
  143. package/dist/generated/stripe.d.ts.map +1 -0
  144. package/dist/generated/stripe.js +150 -0
  145. package/dist/generated/stripe.js.map +1 -0
  146. package/dist/historic.d.ts +40 -0
  147. package/dist/historic.d.ts.map +1 -0
  148. package/dist/historic.js +43 -0
  149. package/dist/historic.js.map +1 -0
  150. package/dist/history.d.ts +96 -0
  151. package/dist/history.d.ts.map +1 -0
  152. package/dist/history.js +213 -0
  153. package/dist/history.js.map +1 -0
  154. package/dist/hmrc.d.ts +28 -0
  155. package/dist/hmrc.d.ts.map +1 -0
  156. package/dist/hmrc.js +108 -0
  157. package/dist/hmrc.js.map +1 -0
  158. package/dist/index.d.ts +11 -0
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +11 -0
  161. package/dist/index.js.map +1 -0
  162. package/dist/klarna.d.ts +31 -0
  163. package/dist/klarna.d.ts.map +1 -0
  164. package/dist/klarna.js +38 -0
  165. package/dist/klarna.js.map +1 -0
  166. package/dist/kysely.d.ts +120 -0
  167. package/dist/kysely.d.ts.map +1 -0
  168. package/dist/kysely.js +309 -0
  169. package/dist/kysely.js.map +1 -0
  170. package/dist/money.d.ts +302 -0
  171. package/dist/money.d.ts.map +1 -0
  172. package/dist/money.js +703 -0
  173. package/dist/money.js.map +1 -0
  174. package/dist/parse.d.ts +55 -0
  175. package/dist/parse.d.ts.map +1 -0
  176. package/dist/parse.js +419 -0
  177. package/dist/parse.js.map +1 -0
  178. package/dist/paypal.d.ts +41 -0
  179. package/dist/paypal.d.ts.map +1 -0
  180. package/dist/paypal.js +58 -0
  181. package/dist/paypal.js.map +1 -0
  182. package/dist/pg.d.ts +100 -0
  183. package/dist/pg.d.ts.map +1 -0
  184. package/dist/pg.js +343 -0
  185. package/dist/pg.js.map +1 -0
  186. package/dist/plugins/adyen.d.ts +15 -0
  187. package/dist/plugins/adyen.d.ts.map +1 -0
  188. package/dist/plugins/adyen.js +10 -0
  189. package/dist/plugins/adyen.js.map +1 -0
  190. package/dist/plugins/all.d.ts +29 -0
  191. package/dist/plugins/all.d.ts.map +1 -0
  192. package/dist/plugins/all.js +30 -0
  193. package/dist/plugins/all.js.map +1 -0
  194. package/dist/plugins/allocate.d.ts +20 -0
  195. package/dist/plugins/allocate.d.ts.map +1 -0
  196. package/dist/plugins/allocate.js +26 -0
  197. package/dist/plugins/allocate.js.map +1 -0
  198. package/dist/plugins/braintree.d.ts +12 -0
  199. package/dist/plugins/braintree.d.ts.map +1 -0
  200. package/dist/plugins/braintree.js +12 -0
  201. package/dist/plugins/braintree.js.map +1 -0
  202. package/dist/plugins/cash.d.ts +19 -0
  203. package/dist/plugins/cash.d.ts.map +1 -0
  204. package/dist/plugins/cash.js +16 -0
  205. package/dist/plugins/cash.js.map +1 -0
  206. package/dist/plugins/checkout.d.ts +21 -0
  207. package/dist/plugins/checkout.d.ts.map +1 -0
  208. package/dist/plugins/checkout.js +16 -0
  209. package/dist/plugins/checkout.js.map +1 -0
  210. package/dist/plugins/convert.d.ts +19 -0
  211. package/dist/plugins/convert.d.ts.map +1 -0
  212. package/dist/plugins/convert.js +13 -0
  213. package/dist/plugins/convert.js.map +1 -0
  214. package/dist/plugins/exact.d.ts +15 -0
  215. package/dist/plugins/exact.d.ts.map +1 -0
  216. package/dist/plugins/exact.js +13 -0
  217. package/dist/plugins/exact.js.map +1 -0
  218. package/dist/plugins/fixed.d.ts +17 -0
  219. package/dist/plugins/fixed.d.ts.map +1 -0
  220. package/dist/plugins/fixed.js +13 -0
  221. package/dist/plugins/fixed.js.map +1 -0
  222. package/dist/plugins/format.d.ts +9 -0
  223. package/dist/plugins/format.d.ts.map +1 -0
  224. package/dist/plugins/format.js +16 -0
  225. package/dist/plugins/format.js.map +1 -0
  226. package/dist/plugins/klarna.d.ts +10 -0
  227. package/dist/plugins/klarna.d.ts.map +1 -0
  228. package/dist/plugins/klarna.js +10 -0
  229. package/dist/plugins/klarna.js.map +1 -0
  230. package/dist/plugins/paypal.d.ts +14 -0
  231. package/dist/plugins/paypal.d.ts.map +1 -0
  232. package/dist/plugins/paypal.js +16 -0
  233. package/dist/plugins/paypal.js.map +1 -0
  234. package/dist/plugins/prisma.d.ts +8 -0
  235. package/dist/plugins/prisma.d.ts.map +1 -0
  236. package/dist/plugins/prisma.js +13 -0
  237. package/dist/plugins/prisma.js.map +1 -0
  238. package/dist/plugins/stripe.d.ts +14 -0
  239. package/dist/plugins/stripe.d.ts.map +1 -0
  240. package/dist/plugins/stripe.js +16 -0
  241. package/dist/plugins/stripe.js.map +1 -0
  242. package/dist/plugins/tax.d.ts +20 -0
  243. package/dist/plugins/tax.d.ts.map +1 -0
  244. package/dist/plugins/tax.js +18 -0
  245. package/dist/plugins/tax.js.map +1 -0
  246. package/dist/policy.d.ts +34 -0
  247. package/dist/policy.d.ts.map +1 -0
  248. package/dist/policy.js +90 -0
  249. package/dist/policy.js.map +1 -0
  250. package/dist/prisma.d.ts +77 -0
  251. package/dist/prisma.d.ts.map +1 -0
  252. package/dist/prisma.js +76 -0
  253. package/dist/prisma.js.map +1 -0
  254. package/dist/processor.d.ts +28 -0
  255. package/dist/processor.d.ts.map +1 -0
  256. package/dist/processor.js +118 -0
  257. package/dist/processor.js.map +1 -0
  258. package/dist/provider.d.ts +103 -0
  259. package/dist/provider.d.ts.map +1 -0
  260. package/dist/provider.js +280 -0
  261. package/dist/provider.js.map +1 -0
  262. package/dist/rate-text.d.ts +38 -0
  263. package/dist/rate-text.d.ts.map +1 -0
  264. package/dist/rate-text.js +143 -0
  265. package/dist/rate-text.js.map +1 -0
  266. package/dist/rate.d.ts +175 -0
  267. package/dist/rate.d.ts.map +1 -0
  268. package/dist/rate.js +371 -0
  269. package/dist/rate.js.map +1 -0
  270. package/dist/rounding-error.d.ts +9 -0
  271. package/dist/rounding-error.d.ts.map +1 -0
  272. package/dist/rounding-error.js +9 -0
  273. package/dist/rounding-error.js.map +1 -0
  274. package/dist/rounding.d.ts +50 -0
  275. package/dist/rounding.d.ts.map +1 -0
  276. package/dist/rounding.js +87 -0
  277. package/dist/rounding.js.map +1 -0
  278. package/dist/schema.d.ts +43 -0
  279. package/dist/schema.d.ts.map +1 -0
  280. package/dist/schema.js +77 -0
  281. package/dist/schema.js.map +1 -0
  282. package/dist/sql.d.ts +54 -0
  283. package/dist/sql.d.ts.map +1 -0
  284. package/dist/sql.js +131 -0
  285. package/dist/sql.js.map +1 -0
  286. package/dist/stripe-fx.d.ts +22 -0
  287. package/dist/stripe-fx.d.ts.map +1 -0
  288. package/dist/stripe-fx.js +30 -0
  289. package/dist/stripe-fx.js.map +1 -0
  290. package/dist/stripe.d.ts +36 -0
  291. package/dist/stripe.d.ts.map +1 -0
  292. package/dist/stripe.js +39 -0
  293. package/dist/stripe.js.map +1 -0
  294. package/dist/tax.d.ts +33 -0
  295. package/dist/tax.d.ts.map +1 -0
  296. package/dist/tax.js +55 -0
  297. package/dist/tax.js.map +1 -0
  298. package/dist/testing/arbitraries.d.ts +43 -0
  299. package/dist/testing/arbitraries.d.ts.map +1 -0
  300. package/dist/testing/arbitraries.js +98 -0
  301. package/dist/testing/arbitraries.js.map +1 -0
  302. package/dist/testing/index.d.ts +67 -0
  303. package/dist/testing/index.d.ts.map +1 -0
  304. package/dist/testing/index.js +159 -0
  305. package/dist/testing/index.js.map +1 -0
  306. package/dist/testing/jest.d.ts +2 -0
  307. package/dist/testing/jest.d.ts.map +1 -0
  308. package/dist/testing/jest.js +15 -0
  309. package/dist/testing/jest.js.map +1 -0
  310. package/dist/testing/vitest.d.ts +7 -0
  311. package/dist/testing/vitest.d.ts.map +1 -0
  312. package/dist/testing/vitest.js +12 -0
  313. package/dist/testing/vitest.js.map +1 -0
  314. package/dist/valibot.d.ts +33 -0
  315. package/dist/valibot.d.ts.map +1 -0
  316. package/dist/valibot.js +61 -0
  317. package/dist/valibot.js.map +1 -0
  318. package/dist/zod.d.ts +36 -0
  319. package/dist/zod.d.ts.map +1 -0
  320. package/dist/zod.js +58 -0
  321. package/dist/zod.js.map +1 -0
  322. package/package.json +280 -5
  323. package/src/adyen-fx.ts +175 -0
  324. package/src/adyen.ts +46 -0
  325. package/src/allocate.ts +393 -0
  326. package/src/bag.ts +170 -0
  327. package/src/boc.ts +136 -0
  328. package/src/boe.ts +131 -0
  329. package/src/braintree.ts +71 -0
  330. package/src/cash.ts +183 -0
  331. package/src/checkout.ts +77 -0
  332. package/src/convert.ts +177 -0
  333. package/src/currency.ts +61 -0
  334. package/src/decimal.ts +283 -0
  335. package/src/drizzle/mysql.ts +78 -0
  336. package/src/drizzle/pg.ts +78 -0
  337. package/src/drizzle/sqlite.ts +70 -0
  338. package/src/ecb.ts +328 -0
  339. package/src/exact.ts +182 -0
  340. package/src/fed.ts +146 -0
  341. package/src/fetch.ts +306 -0
  342. package/src/fixed.ts +558 -0
  343. package/src/fn.ts +94 -0
  344. package/src/format.ts +106 -0
  345. package/src/generated/adyen.ts +144 -0
  346. package/src/generated/braintree.ts +140 -0
  347. package/src/generated/cash.ts +478 -0
  348. package/src/generated/checkout.ts +151 -0
  349. package/src/generated/historic.ts +67 -0
  350. package/src/generated/iso4217.ts +179 -0
  351. package/src/generated/kernel.ts +230 -0
  352. package/src/generated/klarna.ts +21 -0
  353. package/src/generated/paypal.ts +31 -0
  354. package/src/generated/stripe.ts +150 -0
  355. package/src/historic.ts +77 -0
  356. package/src/history.ts +333 -0
  357. package/src/hmrc.ts +125 -0
  358. package/src/index.ts +41 -0
  359. package/src/klarna.ts +54 -0
  360. package/src/kysely.ts +588 -0
  361. package/src/money.ts +979 -0
  362. package/src/parse.ts +511 -0
  363. package/src/paypal.ts +93 -0
  364. package/src/pg.ts +410 -0
  365. package/src/plugins/adyen.ts +25 -0
  366. package/src/plugins/all.ts +57 -0
  367. package/src/plugins/allocate.ts +53 -0
  368. package/src/plugins/braintree.ts +23 -0
  369. package/src/plugins/cash.ts +33 -0
  370. package/src/plugins/checkout.ts +32 -0
  371. package/src/plugins/convert.ts +49 -0
  372. package/src/plugins/exact.ts +23 -0
  373. package/src/plugins/fixed.ts +28 -0
  374. package/src/plugins/format.ts +28 -0
  375. package/src/plugins/klarna.ts +21 -0
  376. package/src/plugins/paypal.ts +29 -0
  377. package/src/plugins/prisma.ts +24 -0
  378. package/src/plugins/stripe.ts +29 -0
  379. package/src/plugins/tax.ts +40 -0
  380. package/src/policy.ts +124 -0
  381. package/src/prisma.ts +199 -0
  382. package/src/processor.ts +181 -0
  383. package/src/provider.ts +389 -0
  384. package/src/rate-text.ts +151 -0
  385. package/src/rate.ts +546 -0
  386. package/src/rounding-error.ts +8 -0
  387. package/src/rounding.ts +122 -0
  388. package/src/schema.ts +119 -0
  389. package/src/sql.ts +178 -0
  390. package/src/stripe-fx.ts +49 -0
  391. package/src/stripe.ts +69 -0
  392. package/src/tax.ts +102 -0
  393. package/src/testing/arbitraries.ts +134 -0
  394. package/src/testing/index.ts +247 -0
  395. package/src/testing/jest.ts +18 -0
  396. package/src/testing/vitest.ts +18 -0
  397. package/src/valibot.ts +87 -0
  398. package/src/zod.ts +78 -0
package/dist/bag.js ADDED
@@ -0,0 +1,122 @@
1
+ // Mixed-currency amounts: a bag of per-currency totals, converted and rounded once when a single
2
+ // total is needed, and a cross-currency comparator through one base currency (a total preorder, so
3
+ // sorting is well defined).
4
+ import { exact, sumExact } from './exact.js';
5
+ import { add } from './money.js';
6
+ import { rateOf } from './provider.js';
7
+ function queryFor(asOf) {
8
+ if (asOf === undefined) {
9
+ return undefined;
10
+ }
11
+ return { asOf };
12
+ }
13
+ /** Per-currency totals of `items`, in first-seen order. */
14
+ export function sumByCurrency(items) {
15
+ const totals = new Map();
16
+ for (const money of items) {
17
+ const previous = totals.get(money.currency);
18
+ if (previous === undefined) {
19
+ totals.set(money.currency, money);
20
+ }
21
+ else {
22
+ totals.set(money.currency, add(previous, money));
23
+ }
24
+ }
25
+ return totals;
26
+ }
27
+ /** An immutable set of per-currency totals: `bag(eur10, usd5, eur2).get('EUR')` is EUR 12. */
28
+ export class MoneyBag {
29
+ #totals;
30
+ /** @internal use bag() */
31
+ constructor(totals) {
32
+ // Copied, so a caller who keeps the Map can't change an immutable bag.
33
+ this.#totals = new Map(totals);
34
+ }
35
+ /** A new bag with `items` added. */
36
+ add(...items) {
37
+ return new MoneyBag(sumByCurrency([...this.#totals.values(), ...items]));
38
+ }
39
+ /** The total in `currency`, or undefined when the bag has none. */
40
+ get(currency) {
41
+ return this.#totals.get(currency);
42
+ }
43
+ /** The currencies held, in first-seen order. */
44
+ currencies() {
45
+ return [...this.#totals.keys()];
46
+ }
47
+ /** The per-currency totals, in first-seen order. */
48
+ values() {
49
+ return [...this.#totals.values()];
50
+ }
51
+ /** Every entry converted exactly into `to` and summed. Nothing is rounded. */
52
+ exactTotal(to, provider, options) {
53
+ const query = queryFor(options?.asOf);
54
+ const parts = [];
55
+ for (const money of this.#totals.values()) {
56
+ if (money.currency === to) {
57
+ parts.push(exact(money));
58
+ }
59
+ else {
60
+ const rate = rateOf(provider, money.currency, to, query);
61
+ parts.push(exact(money).convert(rate));
62
+ }
63
+ }
64
+ return sumExact(parts, to);
65
+ }
66
+ /** The total in `to`, rounded once: `bag.total('EUR', fx, 'halfEven')`. */
67
+ total(to, provider, rounding, options) {
68
+ return this.exactTotal(to, provider, options).round(rounding, options?.scale);
69
+ }
70
+ /** The totals as a list of Money JSON values. */
71
+ toJSON() {
72
+ return this.values().map((money) => money.toJSON());
73
+ }
74
+ }
75
+ /** A bag of the per-currency totals of `items`. */
76
+ export function bag(...items) {
77
+ return new MoneyBag(sumByCurrency(items));
78
+ }
79
+ /** Converts amounts exactly into `options.base`, looking each currency's rate up once. */
80
+ function baseConverter(provider, options) {
81
+ const base = options.base;
82
+ const query = queryFor(options.asOf);
83
+ const ratesToBase = new Map();
84
+ return (money) => {
85
+ if (money.currency === base) {
86
+ return exact(money);
87
+ }
88
+ let rate = ratesToBase.get(money.currency);
89
+ if (rate === undefined) {
90
+ rate = rateOf(provider, money.currency, base, query);
91
+ ratesToBase.set(money.currency, rate);
92
+ }
93
+ return exact(money).convert(rate);
94
+ };
95
+ }
96
+ /**
97
+ * Compares amounts in any currencies through one base currency, exactly (no rounding, so rounding
98
+ * makes no ties). One rate per currency makes it a total preorder, so `list.sort(comparator(fx, {
99
+ * base: 'USD' }))` is consistent. Pairwise conversion with bid/ask or slightly inconsistent mids is
100
+ * not, and Array#sort's result then differs between engines. Rates are looked up once per currency,
101
+ * lazily: a missing rate throws from inside `sort`, after the list may already be partly reordered.
102
+ * `sortByValue` looks every rate up first.
103
+ */
104
+ export function comparator(provider, options) {
105
+ const inBase = baseConverter(provider, options);
106
+ return (money, other) => inBase(money).compare(inBase(other));
107
+ }
108
+ /**
109
+ * A new array of `items` in ascending order of their value in `options.base`, as `comparator`
110
+ * orders them. Every amount is converted before sorting, so a missing rate throws
111
+ * RateUnavailableError before anything is reordered, and each amount is converted only once.
112
+ *
113
+ * sortByValue([jpy100, gbp1, usd1], fx, { base: 'EUR' }) // [jpy100, usd1, gbp1]
114
+ */
115
+ export function sortByValue(items, provider, options) {
116
+ const inBase = baseConverter(provider, options);
117
+ const keyed = Array.from(items, (item) => ({ item, value: inBase(item) }));
118
+ // Array#sort is stable, so equal values keep their input order.
119
+ keyed.sort((first, second) => first.value.compare(second.value));
120
+ return keyed.map((entry) => entry.item);
121
+ }
122
+ //# sourceMappingURL=bag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bag.js","sourceRoot":"","sources":["../src/bag.ts"],"names":[],"mappings":"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"}
package/dist/boc.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { RateTable } from './provider.js';
2
+ /** Options for parseBankOfCanadaJson. */
3
+ export interface BankOfCanadaOptions {
4
+ /**
5
+ * When each day's rates were published, or false for none. Default: 16:30 Toronto time that day,
6
+ * when the Bank says it publishes them.
7
+ */
8
+ readonly availableAt?: false | ((day: string) => string | Date);
9
+ }
10
+ /**
11
+ * Parses the Bank of Canada's daily exchange rates from the Valet API's JSON (the
12
+ * `FX_RATES_DAILY` group, or single `FX…CAD` series) into one CAD-based table per business day,
13
+ * newest first. The Bank quotes CAD per unit of each currency (FXUSDCAD 1.3748), so each table
14
+ * holds the exact inverse, CAD → currency, and keeps the published quote: `rate.inverse()` prints
15
+ * '1.3748' again. Rates have `source: 'BoC'`, `type: 'average'` (an average of a day's quotes, not
16
+ * a market rate) and `availableAt` 16:30 Toronto time, when the Bank says it publishes them (a late
17
+ * rate can come later; pass `availableAt` to change it). Valet leaves a series out of a day
18
+ * without a value, so a series listed in `seriesDetail` but absent on a day, an empty value or a
19
+ * null one, is in that day's `missing`. Anything but daily `FX…CAD` series (monthly or annual
20
+ * groups, other data) throws SyntaxError, as do two different values for one series on one day.
21
+ *
22
+ * Precision: the Bank quotes four decimals, up to six for small rates, so a small rate has only
23
+ * about four significant digits (JPY 0.008760, KRW 0.000929, and IDR 0.000082 with two). Its
24
+ * inverse is exact, but only as precise as that. For such currencies, prefer a source that quotes
25
+ * them the other way round.
26
+ *
27
+ * const tables = parseBankOfCanadaJson(await (await fetch(url)).text());
28
+ * tables[0].rate('CAD', 'USD')?.inverse().toString(); // '1.3748'
29
+ */
30
+ export declare function parseBankOfCanadaJson(json: string, options?: BankOfCanadaOptions): RateTable[];
31
+ //# sourceMappingURL=boc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boc.d.ts","sourceRoot":"","sources":["../src/boc.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AA+B/C,yCAAyC;AACzC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,SAAS,EAAE,CAuE9F"}
package/dist/boc.js ADDED
@@ -0,0 +1,114 @@
1
+ // `@ducatjs/money/convert/boc`: an offline parser for the Bank of Canada's daily exchange rates
2
+ // (the Valet API). No I/O: fetch the JSON yourself, or use fetchBankOfCanada from
3
+ // @ducatjs/money/convert/fetch. Rates are kept exactly as published.
4
+ import { quotedTable } from './history.js';
5
+ import { addDayRate, isCalendarDay, newestFirst, readRateCell } from './rate-text.js';
6
+ const SERIES = /^FX([A-Z]{3})CAD$/;
7
+ const ZERO = /^0+(?:\.0+)?$/;
8
+ // The Bank publishes "once each business day by 16:30 (ET)". One formatter for the zone, built on
9
+ // first use, gives the Toronto offset on any day, summer time included.
10
+ let torontoFormatter;
11
+ /** 16:30 in Toronto on `day`, as a UTC instant. */
12
+ function publishedBy(day) {
13
+ torontoFormatter ??= new Intl.DateTimeFormat('en-US', {
14
+ timeZone: 'America/Toronto',
15
+ timeZoneName: 'shortOffset',
16
+ });
17
+ // Noon UTC is 07:00 or 08:00 in Toronto, after any clock change that day (they happen at 02:00).
18
+ const noon = new Date(`${day}T12:00:00Z`);
19
+ const zone = torontoFormatter.formatToParts(noon).find((part) => part.type === 'timeZoneName');
20
+ const offset = /^GMT([+-]\d+)$/.exec(zone?.value ?? '');
21
+ if (offset === null) {
22
+ throw new RangeError(`Can't tell Toronto's UTC offset on ${day} (${zone?.value})`);
23
+ }
24
+ const utcMs = Date.parse(`${day}T16:30:00Z`) - Number(offset[1]) * 3_600_000;
25
+ return new Date(utcMs).toISOString();
26
+ }
27
+ function notValet(why) {
28
+ throw new SyntaxError(`Not a Bank of Canada Valet daily FX file: ${why}`);
29
+ }
30
+ /**
31
+ * Parses the Bank of Canada's daily exchange rates from the Valet API's JSON (the
32
+ * `FX_RATES_DAILY` group, or single `FX…CAD` series) into one CAD-based table per business day,
33
+ * newest first. The Bank quotes CAD per unit of each currency (FXUSDCAD 1.3748), so each table
34
+ * holds the exact inverse, CAD → currency, and keeps the published quote: `rate.inverse()` prints
35
+ * '1.3748' again. Rates have `source: 'BoC'`, `type: 'average'` (an average of a day's quotes, not
36
+ * a market rate) and `availableAt` 16:30 Toronto time, when the Bank says it publishes them (a late
37
+ * rate can come later; pass `availableAt` to change it). Valet leaves a series out of a day
38
+ * without a value, so a series listed in `seriesDetail` but absent on a day, an empty value or a
39
+ * null one, is in that day's `missing`. Anything but daily `FX…CAD` series (monthly or annual
40
+ * groups, other data) throws SyntaxError, as do two different values for one series on one day.
41
+ *
42
+ * Precision: the Bank quotes four decimals, up to six for small rates, so a small rate has only
43
+ * about four significant digits (JPY 0.008760, KRW 0.000929, and IDR 0.000082 with two). Its
44
+ * inverse is exact, but only as precise as that. For such currencies, prefer a source that quotes
45
+ * them the other way round.
46
+ *
47
+ * const tables = parseBankOfCanadaJson(await (await fetch(url)).text());
48
+ * tables[0].rate('CAD', 'USD')?.inverse().toString(); // '1.3748'
49
+ */
50
+ export function parseBankOfCanadaJson(json, options) {
51
+ const parsed = JSON.parse(json);
52
+ const observations = parsed?.observations;
53
+ if (!Array.isArray(observations)) {
54
+ notValet('no observations');
55
+ }
56
+ // The series the file says it has; each must be a daily FX…CAD series.
57
+ const expected = new Set();
58
+ for (const series of Object.keys(parsed?.seriesDetail ?? {})) {
59
+ if (!SERIES.test(series)) {
60
+ notValet(`series ${series} isn't a daily FX…CAD rate`);
61
+ }
62
+ expected.add(series);
63
+ }
64
+ const days = new Map();
65
+ for (const observation of observations) {
66
+ const day = observation?.['d'];
67
+ if (typeof day !== 'string' || !isCalendarDay(day)) {
68
+ notValet(`bad date ${JSON.stringify(day)}`);
69
+ }
70
+ for (const [series, cell] of Object.entries(observation)) {
71
+ if (series === 'd') {
72
+ continue;
73
+ }
74
+ const currency = SERIES.exec(series)?.[1];
75
+ if (currency === undefined) {
76
+ notValet(`series ${series} isn't a daily FX…CAD rate`);
77
+ }
78
+ const where = `${series} on ${day}`;
79
+ const value = cell?.v;
80
+ if (value !== null && typeof value !== 'string') {
81
+ throw new SyntaxError(`Malformed Valet value for ${where}`);
82
+ }
83
+ const rate = value === null ? null : readRateCell(value, where);
84
+ if (rate !== null && ZERO.test(rate)) {
85
+ throw new SyntaxError(`A zero rate for ${where}`);
86
+ }
87
+ addDayRate(days, day, currency, rate);
88
+ }
89
+ // Valet leaves out a series without a value that day.
90
+ for (const series of expected) {
91
+ const currency = SERIES.exec(series)[1];
92
+ if (days.get(day)?.[currency] === undefined) {
93
+ addDayRate(days, day, currency, null);
94
+ }
95
+ }
96
+ }
97
+ const tables = [];
98
+ for (const [day, rates] of days) {
99
+ if (!Object.values(rates).some((rate) => rate !== null)) {
100
+ continue;
101
+ }
102
+ let availableAt;
103
+ if (options?.availableAt === undefined) {
104
+ availableAt = publishedBy(day);
105
+ }
106
+ else if (options.availableAt !== false) {
107
+ availableAt = options.availableAt(day);
108
+ }
109
+ const info = { base: 'CAD', inverted: true, source: 'BoC', type: 'average', asOf: day };
110
+ tables.push(quotedTable({ ...info, ...(availableAt === undefined ? {} : { availableAt }), rates }));
111
+ }
112
+ return newestFirst(tables);
113
+ }
114
+ //# sourceMappingURL=boc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boc.js","sourceRoot":"","sources":["../src/boc.ts"],"names":[],"mappings":"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"}
package/dist/boe.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { RateTable } from './provider.js';
2
+ /** Options for parseBankOfEnglandCsv. */
3
+ export interface BankOfEnglandOptions {
4
+ /**
5
+ * The decimals the Bank publishes, rebuilt because its CSV strips trailing zeros ('1.35' for
6
+ * 1.3500). Default 4, as its database shows every spot series.
7
+ */
8
+ readonly decimals?: number;
9
+ }
10
+ /**
11
+ * Parses the Bank of England database's CSV of daily spot rates against sterling (series XUDL…,
12
+ * downloaded with codes as column titles: `DATE,XUDLUSS,XUDLERS,…` and dates like `22 Dec 2025`)
13
+ * into one GBP-based table per day, newest first. Rates are currency units per £1, with `source:
14
+ * 'BoE'` and `type: 'spot'`. The CSV strips trailing zeros, so they are rebuilt to `decimals`
15
+ * (default 4). Holidays have no row; an empty cell (before a series starts, or after it ends, as
16
+ * for BGN) is a missing value. An unknown series code, a bad date, two different values for a day
17
+ * or any other unreadable cell throws SyntaxError. No `availableAt` is set: the Bank says only
18
+ * that daily rates are updated "no more than two working days after the date to which they
19
+ * relate".
20
+ *
21
+ * **Licensing: you must check the licence of every Bank of England series you load.** The database
22
+ * is under the Open Government Licence,
23
+ * but the Bank's legal page says "selected exchange rate data and series are excluded from this
24
+ * licence" and "reproduced by the Bank under licence from third parties", and its spot rates
25
+ * page says LSEG and Bloomberg BFIX may be sources, with LSEG content not to be redistributed
26
+ * without LSEG's consent. No series is named either way, so ducat ships no Bank of England data
27
+ * and no fetcher. Parse only files you have downloaded and are allowed to use, and get the Bank's
28
+ * (or LSEG's) permission before you republish any of it. The Bank also says these "are not
29
+ * official rates". For a licensed UK source, see HMRC's monthly rates
30
+ * (@ducatjs/money/convert/hmrc).
31
+ */
32
+ export declare function parseBankOfEnglandCsv(csv: string, options?: BankOfEnglandOptions): RateTable[];
33
+ //# sourceMappingURL=boe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boe.d.ts","sourceRoot":"","sources":["../src/boe.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAkD/C,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,SAAS,EAAE,CA8C9F"}
package/dist/boe.js ADDED
@@ -0,0 +1,114 @@
1
+ // `@ducatjs/money/convert/boe`: an offline parser for the Bank of England's daily spot exchange
2
+ // rates against sterling. Parser only: see parseBankOfEnglandCsv on licensing.
3
+ import { quotedTable } from './history.js';
4
+ import { addDayRate, csvRows, isCalendarDay, newestFirst, readRateCell } from './rate-text.js';
5
+ // The Bank's daily spot series against sterling, by code (currency units per £1).
6
+ const BOE_SERIES = {
7
+ XUDLADS: 'AUD',
8
+ XUDLZOS3: 'BGN',
9
+ XUDLCDS: 'CAD',
10
+ XUDLBK89: 'CNY',
11
+ XUDLBK25: 'CZK',
12
+ XUDLDKS: 'DKK',
13
+ XUDLERS: 'EUR',
14
+ XUDLHDS: 'HKD',
15
+ XUDLBK33: 'HUF',
16
+ XUDLBK97: 'INR',
17
+ XUDLBK78: 'ILS',
18
+ XUDLJYS: 'JPY',
19
+ XUDLBK83: 'MYR',
20
+ XUDLNDS: 'NZD',
21
+ XUDLNKS: 'NOK',
22
+ XUDLBK47: 'PLN',
23
+ XUDLZOS4: 'RON',
24
+ XUDLSRS: 'SAR',
25
+ XUDLSGS: 'SGD',
26
+ XUDLZRS: 'ZAR',
27
+ XUDLBK93: 'KRW',
28
+ XUDLSKS: 'SEK',
29
+ XUDLSFS: 'CHF',
30
+ XUDLTWS: 'TWD',
31
+ XUDLBK87: 'THB',
32
+ XUDLBK95: 'TRY',
33
+ XUDLUSS: 'USD',
34
+ };
35
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
36
+ function notBoe(why) {
37
+ throw new SyntaxError(`Not a Bank of England spot rates CSV file: ${why}`);
38
+ }
39
+ /** '22 Dec 2025' as an ISO date, or SyntaxError. */
40
+ function isoDate(text) {
41
+ const match = /^(\d\d) ([A-Z][a-z]{2}) (\d{4})$/.exec(text.trim());
42
+ const month = match === null ? 0 : MONTHS.indexOf(match[2]) + 1;
43
+ const day = match === null ? '' : `${match[3]}-${String(month).padStart(2, '0')}-${match[1]}`;
44
+ if (month === 0 || !isCalendarDay(day)) {
45
+ notBoe(`bad date ${JSON.stringify(text)}`);
46
+ }
47
+ return day;
48
+ }
49
+ /**
50
+ * Parses the Bank of England database's CSV of daily spot rates against sterling (series XUDL…,
51
+ * downloaded with codes as column titles: `DATE,XUDLUSS,XUDLERS,…` and dates like `22 Dec 2025`)
52
+ * into one GBP-based table per day, newest first. Rates are currency units per £1, with `source:
53
+ * 'BoE'` and `type: 'spot'`. The CSV strips trailing zeros, so they are rebuilt to `decimals`
54
+ * (default 4). Holidays have no row; an empty cell (before a series starts, or after it ends, as
55
+ * for BGN) is a missing value. An unknown series code, a bad date, two different values for a day
56
+ * or any other unreadable cell throws SyntaxError. No `availableAt` is set: the Bank says only
57
+ * that daily rates are updated "no more than two working days after the date to which they
58
+ * relate".
59
+ *
60
+ * **Licensing: you must check the licence of every Bank of England series you load.** The database
61
+ * is under the Open Government Licence,
62
+ * but the Bank's legal page says "selected exchange rate data and series are excluded from this
63
+ * licence" and "reproduced by the Bank under licence from third parties", and its spot rates
64
+ * page says LSEG and Bloomberg BFIX may be sources, with LSEG content not to be redistributed
65
+ * without LSEG's consent. No series is named either way, so ducat ships no Bank of England data
66
+ * and no fetcher. Parse only files you have downloaded and are allowed to use, and get the Bank's
67
+ * (or LSEG's) permission before you republish any of it. The Bank also says these "are not
68
+ * official rates". For a licensed UK source, see HMRC's monthly rates
69
+ * (@ducatjs/money/convert/hmrc).
70
+ */
71
+ export function parseBankOfEnglandCsv(csv, options) {
72
+ const decimals = options?.decimals ?? 4;
73
+ const [header, ...rows] = csvRows(csv.charCodeAt(0) === 0xfeff ? csv.slice(1) : csv);
74
+ if (header === undefined || header[0]?.trim() !== 'DATE' || header.length < 2) {
75
+ notBoe('no DATE column');
76
+ }
77
+ const currencies = header.slice(1).map((code) => {
78
+ const currency = BOE_SERIES[code.trim()];
79
+ if (currency === undefined) {
80
+ notBoe(`unknown series ${code} (use the daily spot series against sterling, XUDL…)`);
81
+ }
82
+ return currency;
83
+ });
84
+ if (new Set(currencies).size !== currencies.length) {
85
+ notBoe('a series appears twice');
86
+ }
87
+ // A download always has a row per business day, so none at all is a cut-off file, not a range
88
+ // of holidays.
89
+ if (rows.length === 0) {
90
+ notBoe('no observations');
91
+ }
92
+ const days = new Map();
93
+ for (const cells of rows) {
94
+ if (cells.length !== header.length) {
95
+ notBoe(`a row has ${cells.length} cells, the header ${header.length}`);
96
+ }
97
+ const day = isoDate(cells[0]);
98
+ currencies.forEach((currency, i) => {
99
+ addDayRate(days, day, currency, readRateCell(cells[i + 1], `${currency} on ${day}`));
100
+ });
101
+ }
102
+ const tables = [];
103
+ for (const [day, rates] of days) {
104
+ if (Object.values(rates).some((rate) => rate !== null)) {
105
+ tables.push(quotedTable({ base: 'GBP', source: 'BoE', type: 'spot', asOf: day, decimals, rates }));
106
+ }
107
+ }
108
+ // A download only has rows for business days, so rows without a single rate aren't data.
109
+ if (tables.length === 0) {
110
+ notBoe('no rates in any row');
111
+ }
112
+ return newestFirst(tables);
113
+ }
114
+ //# sourceMappingURL=boe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boe.js","sourceRoot":"","sources":["../src/boe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Braintree's amount view (https://developer.paypal.com/braintree/docs/reference/general/currencies,
3
+ * encoded in data/braintree.json). `amount` is a decimal string in major units, "formatted like
4
+ * '10' or '10.00'":
5
+ * - zero-decimal currencies can't include decimals: BIF CLP DJF GNF ISK JPY KMF KRW LAK PYG RWF UGX
6
+ * VND VUV XAF XOF XPF (LAK has 2 in ISO 4217);
7
+ * - the others take their ISO decimals ("10.00"). No three-decimal currency is listed.
8
+ * Amounts must be greater than zero, refunds and credits included.
9
+ */
10
+ import { BRAINTREE_EXPONENTS } from './generated/braintree.js';
11
+ import { type CurrencyInput, type Money } from './money.js';
12
+ import type { RoundingMode } from './rounding.js';
13
+ export { BRAINTREE_EXPONENTS };
14
+ /** The number of decimals Braintree takes for a currency (JPY 0, LAK 0, USD 2). */
15
+ export declare function braintreeExponent(currency: string): number;
16
+ /** Whether Braintree lists the currency (case-insensitive). */
17
+ export declare function isBraintreeCurrency(currency: string): boolean;
18
+ /** Options for the Braintree amount: how to round when Braintree needs a coarser amount. */
19
+ export interface BraintreeAmountOptions {
20
+ /**
21
+ * How to round when Braintree needs fewer decimals than the value has (default 'unnecessary':
22
+ * throw).
23
+ */
24
+ readonly rounding?: RoundingMode;
25
+ }
26
+ /**
27
+ * The exact `amount` string: toBraintree(money('10', 'USD')) === '10.00', JPY 100 -> '100'.
28
+ * Braintree has no currency field on a transaction: the merchant account sets it.
29
+ * Throws a RangeError for zero or negative amounts.
30
+ */
31
+ export declare function toBraintree(money: Money, options?: BraintreeAmountOptions): string;
32
+ /** Reads a Braintree amount string exactly: fromBraintree('10.00', 'USD'). */
33
+ export declare function fromBraintree<C extends CurrencyInput>(amount: string, currency: C): Money<Uppercase<C>>;
34
+ //# sourceMappingURL=braintree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"braintree.d.ts","sourceRoot":"","sources":["../src/braintree.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAK/B,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,4FAA4F;AAC5F,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAWlF;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EACnD,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,GACV,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAMrB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Braintree's amount view (https://developer.paypal.com/braintree/docs/reference/general/currencies,
3
+ * encoded in data/braintree.json). `amount` is a decimal string in major units, "formatted like
4
+ * '10' or '10.00'":
5
+ * - zero-decimal currencies can't include decimals: BIF CLP DJF GNF ISK JPY KMF KRW LAK PYG RWF UGX
6
+ * VND VUV XAF XOF XPF (LAK has 2 in ISO 4217);
7
+ * - the others take their ISO decimals ("10.00"). No three-decimal currency is listed.
8
+ * Amounts must be greater than zero, refunds and credits included.
9
+ */
10
+ import { BRAINTREE_EXPONENTS } from './generated/braintree.js';
11
+ import {} from './money.js';
12
+ import { fromProcessorDecimal, toProcessorDecimal } from './processor.js';
13
+ export { BRAINTREE_EXPONENTS };
14
+ /** "Can only contain numbers and one decimal point (e.g. x.xx)". */
15
+ const AMOUNT_PATTERN = /^[0-9]+(\.[0-9]+)?$/;
16
+ /** The number of decimals Braintree takes for a currency (JPY 0, LAK 0, USD 2). */
17
+ export function braintreeExponent(currency) {
18
+ const exponent = BRAINTREE_EXPONENTS[currency.toUpperCase()];
19
+ if (exponent === undefined) {
20
+ throw new RangeError(`${currency} is not a Braintree currency`);
21
+ }
22
+ return exponent;
23
+ }
24
+ /** Whether Braintree lists the currency (case-insensitive). */
25
+ export function isBraintreeCurrency(currency) {
26
+ return BRAINTREE_EXPONENTS[currency.toUpperCase()] !== undefined;
27
+ }
28
+ /**
29
+ * The exact `amount` string: toBraintree(money('10', 'USD')) === '10.00', JPY 100 -> '100'.
30
+ * Braintree has no currency field on a transaction: the merchant account sets it.
31
+ * Throws a RangeError for zero or negative amounts.
32
+ */
33
+ export function toBraintree(money, options) {
34
+ const rounding = options?.rounding ?? 'unnecessary';
35
+ // 81531 "Amount must be greater than zero"; 81501: credits are positive too ("x.xx, not -x.xx").
36
+ return toProcessorDecimal('Braintree', money, braintreeExponent(money.currency), rounding, 'positive');
37
+ }
38
+ /** Reads a Braintree amount string exactly: fromBraintree('10.00', 'USD'). */
39
+ export function fromBraintree(amount, currency) {
40
+ const code = currency.toUpperCase();
41
+ // Validates the currency before parsing, so an unlisted currency is the error reported.
42
+ braintreeExponent(code);
43
+ return fromProcessorDecimal('Braintree', amount, code, AMOUNT_PATTERN);
44
+ }
45
+ //# sourceMappingURL=braintree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"braintree.js","sourceRoot":"","sources":["../src/braintree.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAkC,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG1E,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAE/B,oEAAoE;AACpE,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAE7C,mFAAmF;AACnF,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,IAAI,UAAU,CAAC,GAAG,QAAQ,8BAA8B,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,OAAO,mBAAmB,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,SAAS,CAAC;AACnE,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,OAAgC;IACxE,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,aAAa,CAAC;IAEpD,iGAAiG;IACjG,OAAO,kBAAkB,CACvB,WAAW,EACX,KAAK,EACL,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC,EACjC,QAAQ,EACR,UAAU,CACX,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,QAAW;IAEX,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAkB,CAAC;IAEpD,wFAAwF;IACxF,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxB,OAAO,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;AACzE,CAAC"}
package/dist/cash.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { CASH_RULES, type CashRule } from './generated/cash.js';
2
+ import { type Money, type RoundingPolicy } from './money.js';
3
+ import { type RoundingMode } from './rounding.js';
4
+ /**
5
+ * Rounds to a multiple of `increment` (same currency): roundToIncrement(CHF 10.03, CHF 0.05) = CHF
6
+ * 10.05. Default rounding 'halfExpand' (nearest, ties away from zero), which is what the statutory
7
+ * cash-rounding rules (SE, NO, DK, HU, CZ, FI, BE ...) prescribe.
8
+ */
9
+ export declare function roundToIncrement<C extends string>(money: Money<C>, increment: Money<NoInfer<C>>, rounding?: RoundingMode): Money<C>;
10
+ /**
11
+ * The cash-rounding rule for a currency, optionally narrowed by country (ISO 3166 alpha-2).
12
+ * EUR has no euro-wide rule: pass the country (BE, NL, FI, IE, IT, SK, EE, LT).
13
+ */
14
+ export declare function cashRule(currency: string, country?: string): CashRule | undefined;
15
+ /**
16
+ * Options for `cashRound`: the country (needed for EUR), the rounding mode, and whether to use
17
+ * unverified rules.
18
+ */
19
+ export interface CashRoundOptions {
20
+ readonly country?: string;
21
+ readonly rounding?: RoundingMode;
22
+ /**
23
+ * Apply rules that are only backed by secondary sources (default true; see
24
+ * data/cash-rounding.json).
25
+ */
26
+ readonly includeUnverified?: boolean;
27
+ }
28
+ /**
29
+ * Rounds a cash total to the smallest legal/practical cash step (CHF 0.05, SEK 1, DKK 0.50, CAD
30
+ * 0.05, HUF 5, EUR 0.05 in BE/NL/FI/IE/...). Card and other electronic payments are NOT rounded:
31
+ * only call this for cash tenders. Returns m unchanged when no rule applies.
32
+ */
33
+ export declare function cashRound<C extends string>(money: Money<C>, options?: CashRoundOptions): Money<C>;
34
+ /**
35
+ * The cash-rounding rule for a currency as a `RoundingPolicy`, to pass wherever a rounding mode
36
+ * goes: `m.multiply('1.075', cashPolicy('CHF'))` rounds once, straight to CHF 0.05. The policy only
37
+ * accepts amounts in `currency`. Throws a RangeError when no rule applies (EUR needs a country);
38
+ * use `isoPolicy` for currencies without cash rounding.
39
+ */
40
+ export declare function cashPolicy(currency: string, options?: CashRoundOptions): RoundingPolicy;
41
+ /** Options for `currencyDigits`. */
42
+ export interface CurrencyDigitsOptions {
43
+ /** 'standard' (the default): the ISO 4217 exponent. 'cash': the decimals of a cash amount. */
44
+ readonly usage?: 'standard' | 'cash';
45
+ /** For cash: the country (ISO 3166 alpha-2), which EUR needs. */
46
+ readonly country?: string;
47
+ /** For cash: whether to use rules backed only by secondary sources (default true). */
48
+ readonly includeUnverified?: boolean;
49
+ }
50
+ /**
51
+ * The number of decimals a currency's amounts are shown with: its ISO 4217 exponent, or with
52
+ * `{ usage: 'cash' }`, the decimals of a cash amount (what CLDR calls `cashDigits`): none when cash
53
+ * is rounded to whole units, the exponent otherwise.
54
+ *
55
+ * currencyDigits('SEK') // 2
56
+ * currencyDigits('SEK', { usage: 'cash' }) // 0: cash is rounded to whole kronor
57
+ * currencyDigits('CHF', { usage: 'cash' }) // 2: CHF 0.05 still needs two decimals
58
+ *
59
+ * A currency without a cash rule (or EUR without a country that has one) takes its exponent.
60
+ * Throws for unknown codes, like `currencyExponent`.
61
+ */
62
+ export declare function currencyDigits(currency: string, options?: CurrencyDigitsOptions): number;
63
+ export { CASH_RULES, type CashRule };
64
+ //# sourceMappingURL=cash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cash.d.ts","sourceRoot":"","sources":["../src/cash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAKL,KAAK,KAAK,EACV,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAwC,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAGxF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAC/C,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EACf,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC5B,QAAQ,GAAE,YAA2B,GACpC,KAAK,CAAC,CAAC,CAAC,CAyBV;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAkBjF;AAuBD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAC,CAAC,CAAC,CAOjG;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,cAAc,CAYvF;AAED,oCAAoC;AACpC,MAAM,WAAW,qBAAqB;IACpC,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACrC,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAgBxF;AAED,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,CAAC"}