@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
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `@ducatjs/money/testing/arbitraries`: fast-check arbitraries for property tests. They cover what
3
+ * hand-written cases tend to miss: amounts beyond 2^53 (bigint minor units), currencies with
4
+ * unusual exponents (JPY 0, KWD 3, CLF 4) and scales above the currency's exponent. fast-check is
5
+ * an optional peer dependency, needed only for this entry.
6
+ */
7
+ import fc from 'fast-check';
8
+ import { currencyExponent, ISO_EXPONENTS } from '../currency.js';
9
+ import { MAX_DECIMAL_SCALE } from '../decimal.js';
10
+ import { fromMinor, money } from '../money.js';
11
+ /** A mix of exponents: the common two-decimal currencies and every unusual one. */
12
+ const DEFAULT_CURRENCIES = ['EUR', 'USD', 'GBP', 'JPY', 'KRW', 'KWD', 'BHD', 'TND', 'CLF'];
13
+ /**
14
+ * Currency codes, by default a representative mix of exponents 0, 2, 3 and 4 (JPY, EUR, KWD,
15
+ * CLF...). `{ all: true }` draws from every ISO 4217 code, `extra` adds codes (custom ones from
16
+ * `defineCurrency`), and `exponents` narrows whichever pool that is.
17
+ */
18
+ export function arbCurrency(options = {}) {
19
+ const base = options.all === true ? Object.keys(ISO_EXPONENTS) : DEFAULT_CURRENCIES;
20
+ const pool = [...base, ...(options.extra ?? [])];
21
+ const exponents = options.exponents;
22
+ const codes = exponents === undefined
23
+ ? pool
24
+ : pool.filter((code) => exponents.includes(currencyExponent(code)));
25
+ if (codes.length === 0) {
26
+ throw new RangeError('No currency has the requested exponents');
27
+ }
28
+ return fc.constantFrom(...codes);
29
+ }
30
+ const BIG = 10n ** 30n;
31
+ const SAFE = BigInt(Number.MAX_SAFE_INTEGER);
32
+ /** Minor units weighted toward the interesting places: small, near 2^53, and far beyond it. */
33
+ function arbMinor(min, max) {
34
+ const clamp = (low, high) => fc.bigInt({ min: low > min ? low : min, max: high < max ? high : max });
35
+ const parts = [];
36
+ if (min <= 10000n && max >= -10000n) {
37
+ parts.push(clamp(-10000n, 10000n));
38
+ }
39
+ if (min <= SAFE && max >= -SAFE) {
40
+ parts.push(clamp(-SAFE, SAFE));
41
+ }
42
+ // within 1000 of ±2^53, where number and bigint meet
43
+ for (const edge of [SAFE, -SAFE]) {
44
+ if (min <= edge + 1000n && max >= edge - 1000n) {
45
+ parts.push(clamp(edge - 1000n, edge + 1000n));
46
+ }
47
+ }
48
+ parts.push(fc.bigInt({ min, max }));
49
+ return fc.oneof(...parts);
50
+ }
51
+ /**
52
+ * Money values for property tests: `arbMoney()`, `arbMoney({ currency: 'EUR', min: '0', max:
53
+ * '100' })`. With bounds, every value is within them; without, minor units reach ±10^30.
54
+ */
55
+ export function arbMoney(options = {}) {
56
+ const given = options.currency;
57
+ let currencies;
58
+ if (given === undefined) {
59
+ currencies = arbCurrency();
60
+ }
61
+ else if (typeof given === 'string') {
62
+ currencies = fc.constant(given);
63
+ }
64
+ else {
65
+ currencies = given;
66
+ }
67
+ return currencies.chain((currency) => {
68
+ const exponent = currencyExponent(currency);
69
+ const scaleOption = options.scale;
70
+ let scales;
71
+ if (typeof scaleOption === 'number') {
72
+ scales = fc.constant(scaleOption);
73
+ }
74
+ else if (scaleOption === undefined) {
75
+ scales = fc.oneof({ weight: 3, arbitrary: fc.constant(exponent) }, { weight: 1, arbitrary: fc.integer({ min: exponent + 1, max: exponent + 3 }) });
76
+ }
77
+ else {
78
+ scales = scaleOption;
79
+ }
80
+ return scales.chain((scale) => {
81
+ if (!(Number.isInteger(scale) && scale >= 0 && scale <= MAX_DECIMAL_SCALE)) {
82
+ throw new RangeError(`Invalid scale ${scale}`);
83
+ }
84
+ // Bounds in minor units at this scale; a bound between two units rounds inward.
85
+ const min = options.min === undefined
86
+ ? -BIG
87
+ : BigInt(money(options.min, currency, { scale, rounding: 'ceil' }).toMinor());
88
+ const max = options.max === undefined
89
+ ? BIG
90
+ : BigInt(money(options.max, currency, { scale, rounding: 'floor' }).toMinor());
91
+ if (min > max) {
92
+ throw new RangeError(`No ${currency} amount at scale ${scale} between min and max`);
93
+ }
94
+ return arbMinor(min, max).map((minor) => fromMinor(minor, currency, scale));
95
+ });
96
+ });
97
+ }
98
+ //# sourceMappingURL=arbitraries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arbitraries.js","sourceRoot":"","sources":["../../src/testing/arbitraries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import { type Money } from '../money.js';
2
+ /** An equality tester, as Vitest and Jest's `expect.addEqualityTesters` take them. */
3
+ export type MoneyEqualityTester = (left: unknown, right: unknown) => boolean | undefined;
4
+ /**
5
+ * Money compares by value in `toEqual`, `toStrictEqual` and nested structures: same currency and
6
+ * the same amount, whatever the scale (`1.0` and `1.00`) and whether the minor units are a number
7
+ * or a bigint. For anything other than two Money values it returns undefined, so the usual
8
+ * comparison applies.
9
+ */
10
+ export declare const moneyEquality: MoneyEqualityTester;
11
+ /** A snapshot serializer that prints Money as `12.34 EUR` (unquoted, so it isn't a string). */
12
+ export declare const moneySerializer: {
13
+ test: (value: unknown) => boolean;
14
+ serialize: (value: unknown) => string;
15
+ };
16
+ /** What the matchers receive as `this`, the part of Vitest's and Jest's matcher state used here. */
17
+ export interface MoneyMatcherContext {
18
+ readonly isNot: boolean;
19
+ }
20
+ /** A matcher's result, as Vitest and Jest expect it. */
21
+ export interface MoneyMatcherResult {
22
+ pass: boolean;
23
+ message: () => string;
24
+ actual?: unknown;
25
+ expected?: unknown;
26
+ }
27
+ /** The money matchers, typed for `declare module` augmentation of `Assertion` / `Matchers`. */
28
+ export interface MoneyMatchers<R = unknown> {
29
+ /** The value is Money equal to `amount` in `currency`, at any scale: `toBeMoney('12.34', 'EUR')`. */
30
+ toBeMoney(amount: string | number | bigint, currency: string): R;
31
+ /** The value is Money in `currency`. */
32
+ toHaveCurrency(currency: string): R;
33
+ /** The value is Money greater than `other` (same currency). */
34
+ toBeGreaterThanMoney(other: Money): R;
35
+ /** The value is Money greater than or equal to `other` (same currency). */
36
+ toBeGreaterThanOrEqualMoney(other: Money): R;
37
+ /** The value is Money less than `other` (same currency). */
38
+ toBeLessThanMoney(other: Money): R;
39
+ /** The value is Money less than or equal to `other` (same currency). */
40
+ toBeLessThanOrEqualMoney(other: Money): R;
41
+ /** The value is a list of Money that sums exactly to `total`, as allocations must. */
42
+ toSumTo(total: Money): R;
43
+ }
44
+ /** The matcher implementations, for `expect.extend`. */
45
+ export declare const moneyMatchers: {
46
+ toBeMoney(this: MoneyMatcherContext, received: unknown, amount: string | number | bigint, currency: string): MoneyMatcherResult;
47
+ toHaveCurrency(this: MoneyMatcherContext, received: unknown, currency: string): MoneyMatcherResult;
48
+ toBeGreaterThanMoney: (this: MoneyMatcherContext, received: unknown, other: Money) => MoneyMatcherResult;
49
+ toBeGreaterThanOrEqualMoney: (this: MoneyMatcherContext, received: unknown, other: Money) => MoneyMatcherResult;
50
+ toBeLessThanMoney: (this: MoneyMatcherContext, received: unknown, other: Money) => MoneyMatcherResult;
51
+ toBeLessThanOrEqualMoney: (this: MoneyMatcherContext, received: unknown, other: Money) => MoneyMatcherResult;
52
+ toSumTo(this: MoneyMatcherContext, received: unknown, total: Money): MoneyMatcherResult;
53
+ };
54
+ /** The parts of Vitest's and Jest's `expect` that `installMoneyMatchers` uses. */
55
+ export interface ExpectWithExtensions {
56
+ extend(matchers: any): void;
57
+ addEqualityTesters?(testers: any[]): void;
58
+ addSnapshotSerializer?(serializer: any): void;
59
+ }
60
+ /**
61
+ * Installs everything on a test runner's `expect`: the matchers, value equality and the snapshot
62
+ * serializer. It also gives Money a Node inspect hook, so `console.log(price)` prints
63
+ * `Money(12.34 EUR)`. Safe to call more than once. A runner that lacks `addEqualityTesters` or
64
+ * `addSnapshotSerializer` (Bun supports neither) gets the matchers and the inspect hook.
65
+ */
66
+ export declare function installMoneyMatchers(expect: ExpectWithExtensions): void;
67
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAA8C,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAErF,sFAAsF;AACtF,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,SAAS,CAAC;AAEzF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,mBAO3B,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,eAAe;kBACZ,OAAO,KAAG,OAAO;uBACZ,OAAO,KAAG,MAAM;CACpC,CAAC;AAEF,oGAAoG;AACpG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACxC,qGAAqG;IACrG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IACjE,wCAAwC;IACxC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IACpC,+DAA+D;IAC/D,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;IACtC,2EAA2E;IAC3E,2BAA2B,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;IAC7C,4DAA4D;IAC5D,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;IACnC,wEAAwE;IACxE,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;IAC1C,sFAAsF;IACtF,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC;CAC1B;AA8ED,wDAAwD;AACxD,eAAO,MAAM,aAAa;oBAEhB,mBAAmB,YACf,OAAO,UACT,MAAM,GAAG,MAAM,GAAG,MAAM,YACtB,MAAM,GACf,kBAAkB;yBAOb,mBAAmB,YACf,OAAO,YACP,MAAM,GACf,kBAAkB;iCAzBb,mBAAmB,YAAY,OAAO,SAAS,KAAK,KAAK,kBAAkB;wCAA3E,mBAAmB,YAAY,OAAO,SAAS,KAAK,KAAK,kBAAkB;8BAA3E,mBAAmB,YAAY,OAAO,SAAS,KAAK,KAAK,kBAAkB;qCAA3E,mBAAmB,YAAY,OAAO,SAAS,KAAK,KAAK,kBAAkB;kBA6CrE,mBAAmB,YAAY,OAAO,SAAS,KAAK,GAAG,kBAAkB;CAqBxF,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IAEnC,MAAM,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI,CAAC;IAE5B,kBAAkB,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAE1C,qBAAqB,CAAC,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI,CAAC;CAC/C;AAID;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CA2BvE"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * `@ducatjs/money/testing`: makes Money pleasant to test with Vitest and Jest. It provides value
3
+ * equality for `toEqual`, readable snapshots and failure output (`12.34 EUR`), and money matchers.
4
+ * Call `installMoneyMatchers(expect)` in a setup file, or import `@ducatjs/money/testing/vitest`
5
+ * or `@ducatjs/money/testing/jest`, which do that for you. Nothing here changes production code.
6
+ */
7
+ import { currencyExponent } from '../currency.js';
8
+ import { parseDecimal } from '../decimal.js';
9
+ import { compare, equals, isMoney, money, sum, zero } from '../money.js';
10
+ /**
11
+ * Money compares by value in `toEqual`, `toStrictEqual` and nested structures: same currency and
12
+ * the same amount, whatever the scale (`1.0` and `1.00`) and whether the minor units are a number
13
+ * or a bigint. For anything other than two Money values it returns undefined, so the usual
14
+ * comparison applies.
15
+ */
16
+ export const moneyEquality = (left, right) => {
17
+ if (isMoney(left) && isMoney(right)) {
18
+ return equals(left, right);
19
+ }
20
+ // Anything else, including a Money from a second installed copy of the package (which isMoney
21
+ // does not recognise), is left to the runner's own field-by-field comparison.
22
+ return undefined;
23
+ };
24
+ /** A snapshot serializer that prints Money as `12.34 EUR` (unquoted, so it isn't a string). */
25
+ export const moneySerializer = {
26
+ test: (value) => isMoney(value),
27
+ serialize: (value) => String(value),
28
+ };
29
+ /**
30
+ * Prints a received value for a failure message. JSON.stringify would throw on a bigint or a
31
+ * cycle, and a matcher must fail cleanly, not throw while explaining the failure.
32
+ */
33
+ function show(value, seen = new Set()) {
34
+ if (isMoney(value)) {
35
+ return String(value);
36
+ }
37
+ if (typeof value === 'bigint') {
38
+ return `${value}n`;
39
+ }
40
+ if (typeof value === 'string') {
41
+ return JSON.stringify(value);
42
+ }
43
+ if (typeof value !== 'object' || value === null) {
44
+ return String(value);
45
+ }
46
+ if (seen.has(value)) {
47
+ return '[Circular]';
48
+ }
49
+ seen.add(value);
50
+ if (Array.isArray(value)) {
51
+ return `[${value.map((item) => show(item, seen)).join(', ')}]`;
52
+ }
53
+ const fields = Object.entries(value).map(([key, field]) => `${key}: ${show(field, seen)}`);
54
+ return `{ ${fields.join(', ')} }`;
55
+ }
56
+ function result(context, pass, received, expectation, expected) {
57
+ return {
58
+ pass,
59
+ message: () => `expected ${show(received)} ${context.isNot ? 'not ' : ''}${expectation}`,
60
+ actual: received,
61
+ expected,
62
+ };
63
+ }
64
+ /**
65
+ * Comparing amounts in different currencies, or something that isn't Money, is a mistake in the
66
+ * test, so it throws instead of failing; otherwise `.not` would quietly pass on it.
67
+ */
68
+ function requireComparable(matcher, received, other) {
69
+ if (!isMoney(received) || !isMoney(other)) {
70
+ throw new TypeError(`${matcher}: expected two Money values, got ${show(received)} and ${show(other)}`);
71
+ }
72
+ if (received.currency !== other.currency) {
73
+ throw new TypeError(`${matcher}: cannot compare ${received.currency} with ${other.currency}`);
74
+ }
75
+ }
76
+ /** Decimal places in an amount as `money()` takes it, so `toBeMoney` can keep every digit. */
77
+ function decimalsOf(amount) {
78
+ // parseDecimal understands exponent notation ('1.5e-7'), as money() does
79
+ return typeof amount === 'bigint' ? 0 : parseDecimal(String(amount)).scale;
80
+ }
81
+ function comparison(name, wording, accepts) {
82
+ return function (received, other) {
83
+ requireComparable(name, received, other);
84
+ const pass = accepts(compare(received, other));
85
+ return result(this, pass, received, `${wording} ${show(other)}`);
86
+ };
87
+ }
88
+ /** The matcher implementations, for `expect.extend`. */
89
+ export const moneyMatchers = {
90
+ toBeMoney(received, amount, currency) {
91
+ const scale = Math.max(decimalsOf(amount), currencyExponent(currency));
92
+ const expected = money(amount, currency, { scale });
93
+ const pass = isMoney(received) && equals(received, expected);
94
+ return result(this, pass, received, `to be ${expected}`, expected);
95
+ },
96
+ toHaveCurrency(received, currency) {
97
+ const pass = isMoney(received) && received.currency === currency;
98
+ return result(this, pass, received, `to have currency ${currency}`);
99
+ },
100
+ toBeGreaterThanMoney: comparison('toBeGreaterThanMoney', 'to be greater than', (order) => order > 0),
101
+ toBeGreaterThanOrEqualMoney: comparison('toBeGreaterThanOrEqualMoney', 'to be greater than or equal to', (order) => order >= 0),
102
+ toBeLessThanMoney: comparison('toBeLessThanMoney', 'to be less than', (order) => order < 0),
103
+ toBeLessThanOrEqualMoney: comparison('toBeLessThanOrEqualMoney', 'to be less than or equal to', (order) => order <= 0),
104
+ toSumTo(received, total) {
105
+ // Not a list of Money, or parts in another currency, is a mistake in the test: throw.
106
+ if (!Array.isArray(received) || !isMoney(total)) {
107
+ throw new TypeError(`toSumTo: expected a list of Money and a Money total, got ${show(received)}`);
108
+ }
109
+ for (const part of received) {
110
+ requireComparable('toSumTo', part, total);
111
+ }
112
+ const partsSum = sum(received, total.currency);
113
+ const pass = equals(partsSum, total);
114
+ return {
115
+ pass,
116
+ message: () => `expected ${show(received)} ${this.isNot ? 'not ' : ''}to sum to ${show(total)}` +
117
+ (this.isNot ? '' : `; they sum to ${partsSum}`),
118
+ actual: partsSum,
119
+ expected: total,
120
+ };
121
+ },
122
+ };
123
+ const INSPECT = Symbol.for('nodejs.util.inspect.custom');
124
+ /**
125
+ * Installs everything on a test runner's `expect`: the matchers, value equality and the snapshot
126
+ * serializer. It also gives Money a Node inspect hook, so `console.log(price)` prints
127
+ * `Money(12.34 EUR)`. Safe to call more than once. A runner that lacks `addEqualityTesters` or
128
+ * `addSnapshotSerializer` (Bun supports neither) gets the matchers and the inspect hook.
129
+ */
130
+ export function installMoneyMatchers(expect) {
131
+ expect.extend(moneyMatchers);
132
+ // Optional runner features: Bun has no equality testers, and its addSnapshotSerializer throws
133
+ // "Not implemented". The matchers work without either.
134
+ try {
135
+ expect.addEqualityTesters?.([moneyEquality]);
136
+ }
137
+ catch {
138
+ // not supported by this runner
139
+ }
140
+ try {
141
+ expect.addSnapshotSerializer?.(moneySerializer);
142
+ }
143
+ catch {
144
+ // not supported by this runner
145
+ }
146
+ // The inspect hook is the one change to Money itself; only test setups call this function.
147
+ const prototype = Object.getPrototypeOf(zero('EUR'));
148
+ if (!Object.hasOwn(prototype, INSPECT)) {
149
+ Object.defineProperty(prototype, INSPECT, {
150
+ value() {
151
+ return `Money(${String(this)})`;
152
+ },
153
+ writable: true,
154
+ configurable: true,
155
+ enumerable: false,
156
+ });
157
+ }
158
+ }
159
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from './index.js';
2
+ //# sourceMappingURL=jest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest.d.ts","sourceRoot":"","sources":["../../src/testing/jest.ts"],"names":[],"mappings":"AASA,cAAc,YAAY,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@ducatjs/money/testing/jest`: one import in a Jest setup file (`setupFilesAfterEnv`) installs
3
+ * the money matchers, value equality and the snapshot serializer on Jest's global `expect`. It
4
+ * needs no Jest package of its own. For the types, add `MoneyMatchers` to Jest's `Matchers` in a
5
+ * declaration file (see the README): augmenting Jest's global namespace from here would clash
6
+ * with Vitest's types in projects that have both.
7
+ */
8
+ import { installMoneyMatchers } from './index.js';
9
+ export * from './index.js';
10
+ const globalExpect = globalThis.expect;
11
+ if (globalExpect === undefined) {
12
+ throw new Error('@ducatjs/money/testing/jest needs Jest’s global expect; import it from a setup file, or call installMoneyMatchers(expect)');
13
+ }
14
+ installMoneyMatchers(globalExpect);
15
+ //# sourceMappingURL=jest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest.js","sourceRoot":"","sources":["../../src/testing/jest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAA6B,MAAM,YAAY,CAAC;AAE7E,cAAc,YAAY,CAAC;AAE3B,MAAM,YAAY,GAAI,UAAgD,CAAC,MAAM,CAAC;AAC9E,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;IAC/B,MAAM,IAAI,KAAK,CACb,2HAA2H,CAC5H,CAAC;AACJ,CAAC;AACD,oBAAoB,CAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type MoneyMatchers } from './index.js';
2
+ export * from './index.js';
3
+ declare module 'vitest' {
4
+ interface Matchers<T = any> extends MoneyMatchers<T> {
5
+ }
6
+ }
7
+ //# sourceMappingURL=vitest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../../src/testing/vitest.ts"],"names":[],"mappings":"AAQA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtE,cAAc,YAAY,CAAC;AAE3B,OAAO,QAAQ,QAAQ,CAAC;IAEtB,UAAU,QAAQ,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;KAAG;CACxD"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@ducatjs/money/testing/vitest`: one import in a Vitest setup file installs the money matchers,
3
+ * value equality and the snapshot serializer, and types the matchers on `expect`:
4
+ *
5
+ * // vitest.config.ts: test: { setupFiles: ['./test/setup.ts'] }
6
+ * import '@ducatjs/money/testing/vitest';
7
+ */
8
+ import { expect } from 'vitest';
9
+ import { installMoneyMatchers } from './index.js';
10
+ export * from './index.js';
11
+ installMoneyMatchers(expect);
12
+ //# sourceMappingURL=vitest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../../src/testing/vitest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAsB,MAAM,YAAY,CAAC;AAEtE,cAAc,YAAY,CAAC;AAO3B,oBAAoB,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@ducatjs/money/valibot`: Valibot schemas for the JSON form of Money, `{ "amount": "12.34",
3
+ * "currency": "EUR" }`, for API boundaries. Valibot is an optional peer dependency: only this entry
4
+ * imports it.
5
+ */
6
+ import * as valibot from 'valibot';
7
+ import { type MoneyJSON } from './money.js';
8
+ import { type MoneyFor, type MoneySchemaOptions } from './schema.js';
9
+ export type { MoneyFor, MoneySchemaOptions } from './schema.js';
10
+ declare const jsonShape: valibot.ObjectSchema<{
11
+ readonly amount: valibot.StringSchema<undefined>;
12
+ readonly currency: valibot.StringSchema<undefined>;
13
+ }, undefined>;
14
+ /** The schema type both functions return: the JSON shape, then a transformation. */
15
+ export type MoneySchema<TOutput> = valibot.SchemaWithPipe<readonly [typeof jsonShape, valibot.RawTransformAction<MoneyJSON, TOutput>]>;
16
+ /**
17
+ * Parses `{ amount, currency }` into Money, accepting exactly what the Zod entry accepts (see its
18
+ * `moneySchema`): an exact decimal string with no more decimals than the scale and at most
19
+ * `maxDigits` digits, padded to the scale, and a listed or known currency. Problems are Valibot
20
+ * issues on `amount` or `currency`; other keys are dropped. Bad options throw when the schema is
21
+ * built. Valibot has no codecs, so encode with `money.toJSON()` (or `JSON.stringify`), which this
22
+ * schema reads back.
23
+ *
24
+ * const Order = valibot.object({ total: moneySchema({ currency: 'EUR' }) })
25
+ * v.parse(Order, { total: { amount: '12.34', currency: 'EUR' } }).total // Money<'EUR'>
26
+ */
27
+ export declare function moneySchema<C extends string = string>(options?: MoneySchemaOptions<C>): MoneySchema<MoneyFor<C>>;
28
+ /**
29
+ * Like `moneySchema`, but the output stays the JSON object: it checks that the input would parse,
30
+ * for code that passes the JSON on rather than computing with it.
31
+ */
32
+ export declare function moneyJSONSchema<C extends string = string>(options?: MoneySchemaOptions<C>): MoneySchema<MoneyJSON<C>>;
33
+ //# sourceMappingURL=valibot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valibot.d.ts","sourceRoot":"","sources":["../src/valibot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAGL,KAAK,QAAQ,EAEb,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEhE,QAAA,MAAM,SAAS;;;aAA2E,CAAC;AAE3F,oFAAoF;AACpF,MAAM,MAAM,WAAW,CAAC,OAAO,IAAI,OAAO,CAAC,cAAc,CACvD,SAAS,CAAC,OAAO,SAAS,EAAE,OAAO,CAAC,kBAAkB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAC5E,CAAC;AAeF;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACnD,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC9B,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAc1B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACvD,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC9B,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAc3B"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `@ducatjs/money/valibot`: Valibot schemas for the JSON form of Money, `{ "amount": "12.34",
3
+ * "currency": "EUR" }`, for API boundaries. Valibot is an optional peer dependency: only this entry
4
+ * imports it.
5
+ */
6
+ import * as valibot from 'valibot';
7
+ import { isMoney } from './money.js';
8
+ import { checkSchemaOptions, readMoneyJSON, } from './schema.js';
9
+ const jsonShape = valibot.object({ amount: valibot.string(), currency: valibot.string() });
10
+ /** A Valibot issue path pointing at the field that is wrong. */
11
+ function pathTo(json, issue) {
12
+ return [
13
+ {
14
+ type: 'object',
15
+ origin: 'value',
16
+ input: json,
17
+ key: issue.field,
18
+ value: json[issue.field],
19
+ },
20
+ ];
21
+ }
22
+ /**
23
+ * Parses `{ amount, currency }` into Money, accepting exactly what the Zod entry accepts (see its
24
+ * `moneySchema`): an exact decimal string with no more decimals than the scale and at most
25
+ * `maxDigits` digits, padded to the scale, and a listed or known currency. Problems are Valibot
26
+ * issues on `amount` or `currency`; other keys are dropped. Bad options throw when the schema is
27
+ * built. Valibot has no codecs, so encode with `money.toJSON()` (or `JSON.stringify`), which this
28
+ * schema reads back.
29
+ *
30
+ * const Order = valibot.object({ total: moneySchema({ currency: 'EUR' }) })
31
+ * v.parse(Order, { total: { amount: '12.34', currency: 'EUR' } }).total // Money<'EUR'>
32
+ */
33
+ export function moneySchema(options) {
34
+ checkSchemaOptions(options);
35
+ return valibot.pipe(jsonShape, valibot.rawTransform(({ dataset, addIssue, NEVER }) => {
36
+ const json = dataset.value;
37
+ const result = readMoneyJSON(json, options);
38
+ if (isMoney(result)) {
39
+ return result;
40
+ }
41
+ addIssue({ message: result.message, path: pathTo(json, result) });
42
+ return NEVER;
43
+ }));
44
+ }
45
+ /**
46
+ * Like `moneySchema`, but the output stays the JSON object: it checks that the input would parse,
47
+ * for code that passes the JSON on rather than computing with it.
48
+ */
49
+ export function moneyJSONSchema(options) {
50
+ checkSchemaOptions(options);
51
+ return valibot.pipe(jsonShape, valibot.rawTransform(({ dataset, addIssue, NEVER }) => {
52
+ const json = dataset.value;
53
+ const result = readMoneyJSON(json, options);
54
+ if (isMoney(result)) {
55
+ return json;
56
+ }
57
+ addIssue({ message: result.message, path: pathTo(json, result) });
58
+ return NEVER;
59
+ }));
60
+ }
61
+ //# sourceMappingURL=valibot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valibot.js","sourceRoot":"","sources":["../src/valibot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,OAAO,EAA8B,MAAM,YAAY,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,aAAa,GAId,MAAM,aAAa,CAAC;AAIrB,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAO3F,gEAAgE;AAChE,SAAS,MAAM,CAAC,IAAe,EAAE,KAAiB;IAChD,OAAO;QACL;YACE,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,OAAO;YACf,KAAK,EAAE,IAA0C;YACjD,GAAG,EAAE,KAAK,CAAC,KAAK;YAChB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;SACzB;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,OAA+B;IAE/B,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,OAAO,CAAC,IAAI,CACjB,SAAS,EACT,OAAO,CAAC,YAAY,CAAyB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;QAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,OAAO,MAA8B,CAAC;QACxC,CAAC;QACD,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA+B;IAE/B,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,OAAO,CAAC,IAAI,CACjB,SAAS,EACT,OAAO,CAAC,YAAY,CAA0B,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;QAC7E,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,OAAO,IAAoB,CAAC;QAC9B,CAAC;QACD,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAClE,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
package/dist/zod.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `@ducatjs/money/zod`: Zod schemas for the JSON form of Money, `{ "amount": "12.34", "currency":
3
+ * "EUR" }`, for API boundaries. Zod is an optional peer dependency: only this entry imports it.
4
+ */
5
+ import { z } from 'zod';
6
+ import { type MoneyJSON } from './money.js';
7
+ import { type MoneyFor, type MoneySchemaOptions } from './schema.js';
8
+ export type { MoneyFor, MoneySchemaOptions } from './schema.js';
9
+ declare const jsonShape: z.ZodObject<{
10
+ amount: z.ZodString;
11
+ currency: z.ZodString;
12
+ }, z.core.$strip>;
13
+ /** The schema `moneySchema` returns: a Zod codec between the JSON form and Money. */
14
+ export type MoneyCodec<C extends string> = z.ZodCodec<typeof jsonShape, z.ZodCustom<MoneyFor<C>>>;
15
+ /**
16
+ * Parses `{ amount, currency }` into Money, and encodes Money back to that JSON form. It is a Zod
17
+ * codec, so `schema.parse(json)` (or `z.decode`) gives Money and `z.encode(schema, money)` gives
18
+ * `{ amount, currency }`, written at the amount's own scale.
19
+ *
20
+ * The amount must be an exact decimal string with no more decimals than the scale (the
21
+ * currency's exponent unless `scale` is given; `rounding` allows more), and at most `maxDigits`
22
+ * digits. The currency must be one of `currency`, or any known code. Shorter amounts are padded to
23
+ * the scale ("1.5" EUR gives 1.50 EUR). Problems are Zod issues on `amount` or `currency`; keys
24
+ * other than those two are dropped. Bad options throw when the schema is built.
25
+ *
26
+ * const Order = z.object({ total: moneySchema({ currency: 'EUR' }) })
27
+ * Order.parse({ total: { amount: '12.34', currency: 'EUR' } }).total // Money<'EUR'>
28
+ */
29
+ export declare function moneySchema<C extends string = string>(options?: MoneySchemaOptions<C>): MoneyCodec<C>;
30
+ /**
31
+ * Like `moneySchema`, but the output stays the JSON object (with only `amount` and `currency`): it
32
+ * checks that the input would parse, for code that passes the JSON on rather than computing with
33
+ * it.
34
+ */
35
+ export declare function moneyJSONSchema<C extends string = string>(options?: MoneySchemaOptions<C>): z.ZodType<MoneyJSON<C>, MoneyJSON>;
36
+ //# sourceMappingURL=zod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod.d.ts","sourceRoot":"","sources":["../src/zod.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACxB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEhE,QAAA,MAAM,SAAS;;;iBAAyD,CAAC;AAEzE,qFAAqF;AACrF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAElG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACnD,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC9B,UAAU,CAAC,CAAC,CAAC,CAsBf;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACvD,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC9B,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAUpC"}
package/dist/zod.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `@ducatjs/money/zod`: Zod schemas for the JSON form of Money, `{ "amount": "12.34", "currency":
3
+ * "EUR" }`, for API boundaries. Zod is an optional peer dependency: only this entry imports it.
4
+ */
5
+ import { z } from 'zod';
6
+ import { isMoney } from './money.js';
7
+ import { checkSchemaOptions, readMoneyJSON, } from './schema.js';
8
+ const jsonShape = z.object({ amount: z.string(), currency: z.string() });
9
+ /**
10
+ * Parses `{ amount, currency }` into Money, and encodes Money back to that JSON form. It is a Zod
11
+ * codec, so `schema.parse(json)` (or `z.decode`) gives Money and `z.encode(schema, money)` gives
12
+ * `{ amount, currency }`, written at the amount's own scale.
13
+ *
14
+ * The amount must be an exact decimal string with no more decimals than the scale (the
15
+ * currency's exponent unless `scale` is given; `rounding` allows more), and at most `maxDigits`
16
+ * digits. The currency must be one of `currency`, or any known code. Shorter amounts are padded to
17
+ * the scale ("1.5" EUR gives 1.50 EUR). Problems are Zod issues on `amount` or `currency`; keys
18
+ * other than those two are dropped. Bad options throw when the schema is built.
19
+ *
20
+ * const Order = z.object({ total: moneySchema({ currency: 'EUR' }) })
21
+ * Order.parse({ total: { amount: '12.34', currency: 'EUR' } }).total // Money<'EUR'>
22
+ */
23
+ export function moneySchema(options) {
24
+ checkSchemaOptions(options);
25
+ return z.codec(jsonShape, z.custom((value) => isMoney(value)), {
26
+ decode: (json, payload) => {
27
+ const result = readMoneyJSON(json, options);
28
+ if (isMoney(result)) {
29
+ return result;
30
+ }
31
+ payload.issues.push({
32
+ code: 'custom',
33
+ message: result.message,
34
+ input: json,
35
+ path: [result.field],
36
+ });
37
+ return z.NEVER;
38
+ },
39
+ encode: (value) => value.toJSON(),
40
+ });
41
+ }
42
+ /**
43
+ * Like `moneySchema`, but the output stays the JSON object (with only `amount` and `currency`): it
44
+ * checks that the input would parse, for code that passes the JSON on rather than computing with
45
+ * it.
46
+ */
47
+ export function moneyJSONSchema(options) {
48
+ checkSchemaOptions(options);
49
+ return jsonShape.transform((json, context) => {
50
+ const result = readMoneyJSON(json, options);
51
+ if (isMoney(result)) {
52
+ return json;
53
+ }
54
+ context.addIssue({ code: 'custom', message: result.message, path: [result.field] });
55
+ return z.NEVER;
56
+ });
57
+ }
58
+ //# sourceMappingURL=zod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod.js","sourceRoot":"","sources":["../src/zod.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAA8B,MAAM,YAAY,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,aAAa,GAGd,MAAM,aAAa,CAAC;AAIrB,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAKzE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CACzB,OAA+B;IAE/B,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC,KAAK,CACZ,SAAS,EACT,CAAC,CAAC,MAAM,CAAc,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAChD;QACE,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;YACxB,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpB,OAAO,MAA8B,CAAC;YACxC,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,KAAK,EAAE,IAAI;gBACX,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;aACrB,CAAC,CAAC;YACH,OAAO,CAAC,CAAC,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAE,KAAe,CAAC,MAAM,EAAE;KAC7C,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA+B;IAE/B,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE;QAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,OAAO,IAAoB,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACpF,OAAO,CAAC,CAAC,KAAK,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC"}