@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/src/pg.ts ADDED
@@ -0,0 +1,410 @@
1
+ /**
2
+ * `@ducatjs/money/pg`: node-postgres type parsers that keep NUMERIC and BIGINT exact, a Postgres
3
+ * composite type for Money with a `sum` aggregate, and a guard for NUMERIC(p, s) columns.
4
+ *
5
+ * import pg from 'pg';
6
+ * import { exactTypes } from '@ducatjs/money/pg';
7
+ *
8
+ * const pool = new pg.Pool({ types: exactTypes(pg.types) });
9
+ *
10
+ * It imports nothing from pg at run time: pass `pg.types` in.
11
+ */
12
+ import { currencyExponent } from './currency.js';
13
+ import { money as parseMoney, toDecimal, type CurrencyInput, type Money } from './money.js';
14
+ import { checkDecimalSize, checkIntegerDigits, type DecimalColumnSize } from './sql.js';
15
+
16
+ export { DEFAULT_DECIMAL_SIZE, type DecimalColumnSize } from './sql.js';
17
+
18
+ /** A text-format value parser, as node-postgres calls it. */
19
+ export type TypeParser = (text: string) => unknown;
20
+
21
+ /** The part of `pg.types` (or a client's `types` option) these parsers build on. */
22
+ export interface TypeParserSource {
23
+ getTypeParser(oid: number, format?: 'text' | 'binary'): TypeParser;
24
+ }
25
+
26
+ /** The OIDs of a composite money type and its array type, from `moneyTypeOids`. */
27
+ export interface MoneyTypeOids {
28
+ readonly oid: number;
29
+ readonly arrayOid: number;
30
+ }
31
+
32
+ /** Options for `exactTypes`. */
33
+ export interface ExactTypesOptions {
34
+ /** Also parse this composite money type (and its array) into Money. */
35
+ readonly money?: MoneyTypeOids;
36
+ /**
37
+ * How BIGINT (and BIGINT[]) values come back: `'bigint'` (the default) or `'string'`, pg's own
38
+ * exact format. A bigint is what `fromMinor` takes, but it applies to every BIGINT on the client,
39
+ * `count(*)` and bigserial ids included, and `JSON.stringify` throws on it.
40
+ */
41
+ readonly int8?: 'bigint' | 'string';
42
+ }
43
+
44
+ const INT8_OID = 20;
45
+ const NUMERIC_OID = 1700;
46
+ const INT8_ARRAY_OID = 1016;
47
+ const NUMERIC_ARRAY_OID = 1231;
48
+
49
+ const INTEGER_TEXT = /^-?\d+$/;
50
+ const NUMERIC_TEXT = /^-?\d+(?:\.\d+)?$/;
51
+
52
+ /**
53
+ * BIGINT text as a bigint. node-postgres returns it as a string by default, which is exact but not
54
+ * a number type the Money constructors accept.
55
+ */
56
+ export function parseBigInt(text: string): bigint {
57
+ if (!INTEGER_TEXT.test(text)) {
58
+ throw new SyntaxError(`Expected BIGINT text, got "${text}"`);
59
+ }
60
+ return BigInt(text);
61
+ }
62
+
63
+ /** BIGINT text, checked and kept as the string. */
64
+ function checkBigIntText(text: string): string {
65
+ parseBigInt(text);
66
+ return text;
67
+ }
68
+
69
+ /**
70
+ * NUMERIC text, checked and returned as the exact decimal string. Postgres NUMERIC can hold `NaN`
71
+ * and `±Infinity`, which are not amounts, so they throw.
72
+ */
73
+ export function parseNumeric(text: string): string {
74
+ if (!NUMERIC_TEXT.test(text)) {
75
+ throw new SyntaxError(`Expected a finite NUMERIC, got "${text}"`);
76
+ }
77
+ return text;
78
+ }
79
+
80
+ /**
81
+ * Splits Postgres array text (`{1,2,NULL}`, `{{1,2},{3,4}}`, `{"(1.00,EUR)"}`) into nested arrays of
82
+ * element text, with `null` for NULL. Quoted elements are unescaped.
83
+ */
84
+ export function parseArray(text: string): unknown[] {
85
+ let position = 0;
86
+
87
+ // an array may start with explicit bounds, `[1:2]={...}`
88
+ if (text.startsWith('[')) {
89
+ position = text.indexOf('=') + 1;
90
+ }
91
+
92
+ function parseLevel(): unknown[] {
93
+ if (text[position] !== '{') {
94
+ throw new SyntaxError(`Malformed array text "${text}"`);
95
+ }
96
+ position++;
97
+ const items: unknown[] = [];
98
+ if (text[position] === '}') {
99
+ position++;
100
+ return items;
101
+ }
102
+
103
+ for (;;) {
104
+ if (text[position] === '{') {
105
+ items.push(parseLevel());
106
+ } else if (text[position] === '"') {
107
+ position++;
108
+ let value = '';
109
+ while (text[position] !== '"') {
110
+ if (position >= text.length) {
111
+ throw new SyntaxError(`Unterminated quote in array text "${text}"`);
112
+ }
113
+ if (text[position] === '\\') {
114
+ position++;
115
+ }
116
+ value += text[position];
117
+ position++;
118
+ }
119
+ position++;
120
+ items.push(value);
121
+ } else {
122
+ const start = position;
123
+ while (position < text.length && text[position] !== ',' && text[position] !== '}') {
124
+ position++;
125
+ }
126
+ const value = text.slice(start, position);
127
+ items.push(value === 'NULL' ? null : value);
128
+ }
129
+
130
+ if (text[position] === ',') {
131
+ position++;
132
+ continue;
133
+ }
134
+ if (text[position] === '}') {
135
+ position++;
136
+ return items;
137
+ }
138
+ throw new SyntaxError(`Malformed array text "${text}"`);
139
+ }
140
+ }
141
+
142
+ const result = parseLevel();
143
+ if (position !== text.length) {
144
+ throw new SyntaxError(`Malformed array text "${text}"`);
145
+ }
146
+ return result;
147
+ }
148
+
149
+ function mapArray(items: unknown[], parseElement: (text: string) => unknown): unknown[] {
150
+ return items.map((item) => {
151
+ if (item === null) {
152
+ return null;
153
+ }
154
+ if (Array.isArray(item)) {
155
+ return mapArray(item, parseElement);
156
+ }
157
+ return parseElement(item as string);
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Splits record text, `(12.34,EUR)` or `(12.34,"A\\B")`, into its fields, with `null` for an empty
163
+ * unquoted field. Quoted fields are unescaped: Postgres doubles `"` and `\` inside them on output,
164
+ * and accepts a backslash before any character on input.
165
+ */
166
+ function parseRecord(text: string): (string | null)[] {
167
+ if (!text.startsWith('(') || !text.endsWith(')')) {
168
+ throw new SyntaxError(`Expected record text in parentheses, got "${text}"`);
169
+ }
170
+ const fields: (string | null)[] = [];
171
+ let position = 1;
172
+ const end = text.length - 1;
173
+
174
+ for (;;) {
175
+ let field = '';
176
+ let isQuotedOrNonEmpty = false;
177
+ while (position < end && text[position] !== ',') {
178
+ const character = text[position]!;
179
+ if (character === '"') {
180
+ isQuotedOrNonEmpty = true;
181
+ position++;
182
+ for (;;) {
183
+ if (position >= end) {
184
+ throw new SyntaxError(`Unbalanced quote in record text "${text}"`);
185
+ }
186
+ const quoted = text[position]!;
187
+ if (quoted === '\\') {
188
+ field += text[position + 1] ?? '';
189
+ position += 2;
190
+ } else if (quoted === '"' && text[position + 1] === '"') {
191
+ field += '"';
192
+ position += 2;
193
+ } else if (quoted === '"') {
194
+ position++;
195
+ break;
196
+ } else {
197
+ field += quoted;
198
+ position++;
199
+ }
200
+ }
201
+ continue;
202
+ }
203
+ if (character === '\\') {
204
+ field += text[position + 1] ?? '';
205
+ position += 2;
206
+ } else {
207
+ field += character;
208
+ position++;
209
+ }
210
+ isQuotedOrNonEmpty = true;
211
+ }
212
+ fields.push(isQuotedOrNonEmpty ? field : null);
213
+ if (position >= end) {
214
+ return fields;
215
+ }
216
+ position++;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Money from the text of the composite type, `(12.34,EUR)`. The amount keeps the scale it was
222
+ * stored with, and never less than the currency's exponent. NULL fields, `NaN`, `Infinity` and
223
+ * malformed text throw.
224
+ */
225
+ export function parseMoneyComposite(text: string): Money {
226
+ const fields = parseRecord(text);
227
+ if (fields.length !== 2) {
228
+ throw new SyntaxError(`Expected (amount,currency) composite text, got "${text}"`);
229
+ }
230
+ const [amount, currency] = fields;
231
+ if (amount === null || amount === undefined || currency === null || currency === undefined) {
232
+ throw new TypeError(`A money composite needs both an amount and a currency, got "${text}"`);
233
+ }
234
+ parseNumeric(amount);
235
+
236
+ const fractionDigits = amount.includes('.') ? amount.length - amount.indexOf('.') - 1 : 0;
237
+ const scale = Math.max(fractionDigits, currencyExponent(currency));
238
+ return parseMoney(amount, currency, { scale });
239
+ }
240
+
241
+ /**
242
+ * The composite type's text for a query parameter: `(12.34,"EUR")`, at the Money's own scale. The
243
+ * currency is always quoted and escaped, so custom codes containing `\`, `"`, `,` or `)` survive.
244
+ * Cast it in SQL (`$1::ducat_money`) where Postgres can't infer the type.
245
+ */
246
+ export function toMoneyComposite(money: Money): string {
247
+ const currency = money.currency.replace(/["\\]/g, (character) => `\\${character}`);
248
+ return `(${toDecimal(money)},"${currency}")`;
249
+ }
250
+
251
+ /**
252
+ * Type parsers for node-postgres that never turn an amount into a float: BIGINT as bigint, NUMERIC
253
+ * as an exact string (rejecting `NaN` and `Infinity`), their arrays the same way (pg's default
254
+ * parses NUMERIC arrays as floats), and optionally the composite money type as Money. Every other
255
+ * type uses `base`.
256
+ *
257
+ * new pg.Pool({ types: exactTypes(pg.types, { money: await moneyTypeOids(client) }) })
258
+ */
259
+ export function exactTypes(base: TypeParserSource, options?: ExactTypesOptions): TypeParserSource {
260
+ const moneyOids = options?.money;
261
+ const parseInt8 = options?.int8 === 'string' ? checkBigIntText : parseBigInt;
262
+
263
+ return {
264
+ getTypeParser(oid: number, format?: 'text' | 'binary'): TypeParser {
265
+ // the binary format is only used when a query asks for it; leave it to pg
266
+ if (format === 'binary') {
267
+ return base.getTypeParser(oid, format);
268
+ }
269
+ if (oid === INT8_OID) {
270
+ return parseInt8;
271
+ }
272
+ if (oid === NUMERIC_OID) {
273
+ return parseNumeric;
274
+ }
275
+ if (oid === INT8_ARRAY_OID) {
276
+ return (text) => mapArray(parseArray(text), parseInt8);
277
+ }
278
+ if (oid === NUMERIC_ARRAY_OID) {
279
+ return (text) => mapArray(parseArray(text), parseNumeric);
280
+ }
281
+ if (moneyOids !== undefined && oid === moneyOids.oid) {
282
+ return parseMoneyComposite;
283
+ }
284
+ if (moneyOids !== undefined && oid === moneyOids.arrayOid) {
285
+ return (text) => mapArray(parseArray(text), parseMoneyComposite);
286
+ }
287
+ return base.getTypeParser(oid, format);
288
+ },
289
+ };
290
+ }
291
+
292
+ /** The default name of the composite money type. */
293
+ export const MONEY_TYPE_NAME = 'ducat_money';
294
+
295
+ const IDENTIFIER = /^[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)?$/;
296
+ // Postgres truncates longer names, which would make `name` and `name_add` collide with others
297
+ const MAX_IDENTIFIER_LENGTH = 63;
298
+
299
+ /**
300
+ * The DDL for a composite money type, `(amount numeric, currency text)`, and a `sum` aggregate over
301
+ * it that throws on mixed currencies, and on a value with a NULL amount or currency. The amount is
302
+ * an unconstrained NUMERIC, so it keeps every digit and its scale. A schema-qualified name puts the
303
+ * type, its add function and the aggregate in that schema.
304
+ *
305
+ * Run the statements once, in order, in a migration: they are not idempotent, and a second run
306
+ * fails with `duplicate_object`.
307
+ *
308
+ * for (const statement of moneyTypeSql()) await client.query(statement);
309
+ * SELECT sum(price) FROM items; -- (37.50,EUR)
310
+ */
311
+ export function moneyTypeSql(name: string = MONEY_TYPE_NAME): string[] {
312
+ if (!IDENTIFIER.test(name)) {
313
+ throw new RangeError(`"${name}" is not a plain lower-case SQL name (optionally schema.name)`);
314
+ }
315
+ const dot = name.indexOf('.');
316
+ const schemaPrefix = dot < 0 ? '' : name.slice(0, dot + 1);
317
+ const typeName = name.slice(dot + 1);
318
+ if (`${typeName}_add`.length > MAX_IDENTIFIER_LENGTH) {
319
+ throw new RangeError(`"${typeName}" is too long for a Postgres name with an "_add" suffix`);
320
+ }
321
+ const add = `${name}_add`;
322
+
323
+ return [
324
+ `CREATE TYPE ${name} AS (amount numeric, currency text)`,
325
+ // search_path FROM CURRENT: the cast below resolves the same way whatever the caller's path
326
+ `CREATE FUNCTION ${add}(total ${name}, item ${name}) RETURNS ${name}
327
+ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE SET search_path FROM CURRENT AS $$
328
+ BEGIN
329
+ IF item IS NULL THEN
330
+ RETURN total;
331
+ END IF;
332
+ IF item.amount IS NULL OR item.currency IS NULL THEN
333
+ RAISE EXCEPTION 'a money value needs both an amount and a currency'
334
+ USING ERRCODE = 'null_value_not_allowed';
335
+ END IF;
336
+ IF total IS NULL THEN
337
+ RETURN item;
338
+ END IF;
339
+ IF total.currency IS DISTINCT FROM item.currency THEN
340
+ RAISE EXCEPTION 'cannot add % and % amounts', total.currency, item.currency
341
+ USING ERRCODE = 'data_exception';
342
+ END IF;
343
+ RETURN ROW(total.amount + item.amount, total.currency)::${name};
344
+ END
345
+ $$`,
346
+ `CREATE AGGREGATE ${schemaPrefix}sum(${name}) ` +
347
+ `(SFUNC = ${add}, STYPE = ${name}, COMBINEFUNC = ${add}, PARALLEL = SAFE)`,
348
+ ];
349
+ }
350
+
351
+ /** The part of a pg Client or Pool that `moneyTypeOids` uses. */
352
+ export interface Queryable {
353
+ query(
354
+ sql: string,
355
+ values: unknown[],
356
+ ): Promise<{ rows: { oid: number | string; typarray: number | string }[] }>;
357
+ }
358
+
359
+ /**
360
+ * Looks up the OIDs of the composite money type, for `exactTypes`. Run it once at start-up on a
361
+ * client without the money parser. OIDs differ between databases, and change if the type is
362
+ * dropped and recreated; values of a type whose OID `exactTypes` wasn't given come back as pg's
363
+ * raw text. With a type per schema, look up and pass each one's OIDs.
364
+ */
365
+ export async function moneyTypeOids(
366
+ client: Queryable,
367
+ name: string = MONEY_TYPE_NAME,
368
+ ): Promise<MoneyTypeOids> {
369
+ const result = await client.query(
370
+ 'SELECT oid, typarray FROM pg_type WHERE oid = to_regtype($1)',
371
+ [name],
372
+ );
373
+ const row = result.rows[0];
374
+ if (row === undefined) {
375
+ throw new RangeError(`No type "${name}" in this database; run moneyTypeSql() first`);
376
+ }
377
+ return { oid: Number(row.oid), arrayOid: Number(row.typarray) };
378
+ }
379
+
380
+ /**
381
+ * The exact decimal string for a NUMERIC(precision, scale) column, or a throw if Postgres would
382
+ * change it: it rounds extra decimals silently on insert, and rejects too many integer digits.
383
+ * Unlike the column types, any scale up to the column's is accepted, and trailing zeros beyond it
384
+ * are dropped since they don't change the value.
385
+ */
386
+ export function toNumeric<C extends CurrencyInput>(
387
+ money: Money<C>,
388
+ size: DecimalColumnSize,
389
+ ): string {
390
+ checkDecimalSize(money.currency, size);
391
+ const decimal = toDecimal(money);
392
+ const [integerPart, fraction = ''] = decimal.replace('-', '').split('.');
393
+
394
+ const significantFraction = fraction.replace(/0+$/, '');
395
+ if (significantFraction.length > size.scale) {
396
+ throw new RangeError(
397
+ `${money} has ${significantFraction.length} decimals; NUMERIC(${size.precision},${size.scale}) ` +
398
+ `would round it to ${size.scale}`,
399
+ );
400
+ }
401
+
402
+ checkIntegerDigits(money, decimal, size);
403
+
404
+ if (fraction.length <= size.scale) {
405
+ return decimal;
406
+ }
407
+ const sign = decimal.startsWith('-') ? '-' : '';
408
+ const kept = fraction.slice(0, size.scale);
409
+ return kept === '' ? `${sign}${integerPart}` : `${sign}${integerPart}.${kept}`;
410
+ }
@@ -0,0 +1,25 @@
1
+ /** `@ducatjs/money/adyen`: Adyen's minor-unit view. Importing this module adds `toAdyen`. */
2
+ import { toAdyen } from '../adyen.js';
3
+ import { extendMoney, type Money } from '../money.js';
4
+ import type { RoundingMode } from '../rounding.js';
5
+
6
+ export * from '../adyen.js';
7
+
8
+ declare module '../money.js' {
9
+ interface Money<C extends string> {
10
+ /**
11
+ * Adyen `amount` object: `money('15000', 'IDR').toAdyen()` is `{ value: 15000, currency: 'IDR'
12
+ * }`.
13
+ */
14
+ toAdyen(rounding?: RoundingMode): { value: number; currency: string };
15
+ }
16
+ }
17
+
18
+ extendMoney(
19
+ {
20
+ toAdyen(this: Money, rounding?: RoundingMode) {
21
+ return toAdyen(this, rounding);
22
+ },
23
+ },
24
+ { owner: '@ducatjs/money/adyen' },
25
+ );
@@ -0,0 +1,57 @@
1
+ /** `@ducatjs/money/all`: core plus every opt-in module, with all methods installed. */
2
+ export * from '../index.js';
3
+ // The core Money methods as plain functions too, as in /fn: /all is everything.
4
+ export {
5
+ add,
6
+ subtract,
7
+ negate,
8
+ abs,
9
+ multiply,
10
+ divide,
11
+ round,
12
+ toScale,
13
+ compare,
14
+ equals,
15
+ identical,
16
+ lessThan,
17
+ lessThanOrEqual,
18
+ greaterThan,
19
+ greaterThanOrEqual,
20
+ isZero,
21
+ isPositive,
22
+ isNegative,
23
+ sign,
24
+ min,
25
+ max,
26
+ clamp,
27
+ majorPart,
28
+ minorPart,
29
+ toDecimal,
30
+ toMinor,
31
+ toMinorNumber,
32
+ } from '../money.js';
33
+ export { roundingPolicy, isoPolicy, assertScale, type RoundingPolicyOptions } from '../policy.js';
34
+ export * from './allocate.js';
35
+ export * from './tax.js';
36
+ export * from './convert.js';
37
+ export * from './exact.js';
38
+ export * from '../ecb.js';
39
+ export * from '../boc.js';
40
+ export * from '../boe.js';
41
+ export * from '../hmrc.js';
42
+ export * from '../fed.js';
43
+ export * from '../history.js';
44
+ export * from '../stripe-fx.js';
45
+ export * from '../adyen-fx.js';
46
+ export * from './cash.js';
47
+ export * from '../historic.js';
48
+ export * from './format.js';
49
+ export * from '../parse.js';
50
+ export * from './stripe.js';
51
+ export * from './adyen.js';
52
+ export * from './checkout.js';
53
+ export * from './paypal.js';
54
+ export * from './braintree.js';
55
+ export * from './klarna.js';
56
+ export * from './prisma.js';
57
+ export * from './fixed.js';
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `@ducatjs/money/allocate`: allocation, splitting and tranches. Importing this module adds the
3
+ * methods below to every Money; the free functions are exported too (and from `@ducatjs/money/fn`,
4
+ * which changes no prototypes).
5
+ */
6
+ import {
7
+ allocate,
8
+ chunk,
9
+ instalments,
10
+ schedule,
11
+ split,
12
+ type AllocateOptions,
13
+ type InstalmentOptions,
14
+ type Tranche,
15
+ type Weight,
16
+ } from '../allocate.js';
17
+ import { extendMoney, type Money } from '../money.js';
18
+
19
+ export * from '../allocate.js';
20
+
21
+ declare module '../money.js' {
22
+ interface Money<C extends string> {
23
+ /** Splits by weights; the parts always sum to this. */
24
+ allocate(weights: readonly Weight[], options?: AllocateOptions): Money<C>[];
25
+ /** Splits into `count` near-equal parts that sum to this. */
26
+ split(count: number, options?: AllocateOptions): Money<C>[];
27
+ instalments(count: number, options?: InstalmentOptions<C>): Money<C>[];
28
+ schedule(tranches: readonly Tranche<C>[]): Money<C>[];
29
+ /** Tranches of at most `max`: 1000 by 300 gives 300, 300, 300, 100. */
30
+ chunk(max: Money<C>): Money<C>[];
31
+ }
32
+ }
33
+
34
+ extendMoney(
35
+ {
36
+ allocate(this: Money, weights: readonly Weight[], options?: AllocateOptions) {
37
+ return allocate(this, weights, options);
38
+ },
39
+ split(this: Money, count: number, options?: AllocateOptions) {
40
+ return split(this, count, options);
41
+ },
42
+ instalments(this: Money, count: number, options?: InstalmentOptions<string>) {
43
+ return instalments(this, count, options);
44
+ },
45
+ schedule(this: Money, tranches: readonly Tranche<string>[]) {
46
+ return schedule(this, tranches);
47
+ },
48
+ chunk(this: Money, max: Money) {
49
+ return chunk(this, max);
50
+ },
51
+ },
52
+ { owner: '@ducatjs/money/allocate' },
53
+ );
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@ducatjs/money/braintree`: Braintree's amount view. Importing this module adds `toBraintree`.
3
+ */
4
+ import { toBraintree, type BraintreeAmountOptions } from '../braintree.js';
5
+ import { extendMoney, type Money } from '../money.js';
6
+
7
+ export * from '../braintree.js';
8
+
9
+ declare module '../money.js' {
10
+ interface Money<C extends string> {
11
+ /** The exact `amount` string for Braintree: `money('10', 'USD').toBraintree()` is '10.00'. */
12
+ toBraintree(options?: BraintreeAmountOptions): string;
13
+ }
14
+ }
15
+
16
+ extendMoney(
17
+ {
18
+ toBraintree(this: Money, options?: BraintreeAmountOptions) {
19
+ return toBraintree(this, options);
20
+ },
21
+ },
22
+ { owner: '@ducatjs/money/braintree' },
23
+ );
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@ducatjs/money/cash`: cash rounding (cash tenders only; card payments are never rounded) and
3
+ * rounding to an increment. Importing this module adds the methods below.
4
+ */
5
+ import { cashRound, roundToIncrement, type CashRoundOptions } from '../cash.js';
6
+ import { extendMoney, type Money } from '../money.js';
7
+ import type { RoundingMode } from '../rounding.js';
8
+
9
+ export * from '../cash.js';
10
+
11
+ declare module '../money.js' {
12
+ interface Money<C extends string> {
13
+ /**
14
+ * Rounds a cash total to the local cash step (CHF 0.05, SEK 1, ...); unchanged when no rule
15
+ * applies.
16
+ */
17
+ cashRound(options?: CashRoundOptions): Money<C>;
18
+ /** Rounds to a multiple of `increment`, e.g. CHF 10.03 to CHF 0.05 gives CHF 10.05. */
19
+ roundToIncrement(increment: Money<C>, rounding?: RoundingMode): Money<C>;
20
+ }
21
+ }
22
+
23
+ extendMoney(
24
+ {
25
+ cashRound(this: Money, options?: CashRoundOptions) {
26
+ return cashRound(this, options);
27
+ },
28
+ roundToIncrement(this: Money, increment: Money, rounding?: RoundingMode) {
29
+ return roundToIncrement(this, increment, rounding);
30
+ },
31
+ },
32
+ { owner: '@ducatjs/money/cash' },
33
+ );
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@ducatjs/money/checkout`: Checkout.com's amount view. Importing this module adds `toCheckout`
3
+ * and `toCheckoutAmount`.
4
+ */
5
+ import { toCheckout, toCheckoutAmount, type CheckoutAmountOptions } from '../checkout.js';
6
+ import { extendMoney, type Money } from '../money.js';
7
+
8
+ export * from '../checkout.js';
9
+
10
+ declare module '../money.js' {
11
+ interface Money<C extends string> {
12
+ /**
13
+ * The integer `amount` to send to Checkout.com: `money('5000', 'CLP').toCheckoutAmount()` is
14
+ * 500000.
15
+ */
16
+ toCheckoutAmount(options?: CheckoutAmountOptions): number;
17
+ /** `{ amount, currency }` for a Checkout.com payment request. */
18
+ toCheckout(options?: CheckoutAmountOptions): { amount: number; currency: string };
19
+ }
20
+ }
21
+
22
+ extendMoney(
23
+ {
24
+ toCheckoutAmount(this: Money, options?: CheckoutAmountOptions) {
25
+ return toCheckoutAmount(this, options);
26
+ },
27
+ toCheckout(this: Money, options?: CheckoutAmountOptions) {
28
+ return toCheckout(this, options);
29
+ },
30
+ },
31
+ { owner: '@ducatjs/money/checkout' },
32
+ );
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `@ducatjs/money/convert`: currency conversion at exact rates. Importing this module adds
3
+ * `Money#convert`.
4
+ */
5
+ import { convert } from '../convert.js';
6
+ import type { Factor } from '../decimal.js';
7
+ import { extendMoney, type CurrencyInput, type Money, type RoundingInput } from '../money.js';
8
+ import type { ProviderConvertOptions } from '../convert.js';
9
+ import type { RateProvider } from '../provider.js';
10
+ import type { ConvertOptions, Rate } from '../rate.js';
11
+
12
+ export * from '../convert.js';
13
+
14
+ declare module '../money.js' {
15
+ interface Money<C extends string> {
16
+ /** Converts at a Rate, rounding once: `eur.convert(eurUsd, 'halfEven')`. */
17
+ convert<To extends string>(
18
+ rate: Rate<NoInfer<C>, To>,
19
+ rounding?: RoundingInput,
20
+ options?: ConvertOptions,
21
+ ): Money<To>;
22
+ /** Converts at an exact decimal-string rate: `eur.convert('USD', '1.0832', 'halfEven')`. */
23
+ convert<To extends CurrencyInput>(
24
+ to: To,
25
+ rate: Factor,
26
+ rounding?: RoundingInput,
27
+ options?: ConvertOptions,
28
+ ): Money<To>;
29
+ /**
30
+ * Converts through a provider, rounding once: `usd.convert('JPY', via('EUR', ecb),
31
+ * 'halfEven')`.
32
+ */
33
+ convert<To extends CurrencyInput>(
34
+ to: To,
35
+ provider: RateProvider,
36
+ rounding?: RoundingInput,
37
+ options?: ProviderConvertOptions,
38
+ ): Money<To>;
39
+ }
40
+ }
41
+
42
+ extendMoney(
43
+ {
44
+ convert(this: Money, ...args: [never, never?, never?, never?]) {
45
+ return (convert as (money: Money, ...rest: unknown[]) => Money)(this, ...args);
46
+ },
47
+ },
48
+ { owner: '@ducatjs/money/convert' },
49
+ );