@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,102 @@
1
+ import type { CurrencyInput, Money } from '../money.js';
2
+ /**
3
+ * An INTEGER column of minor units in a fixed currency (`1234` is 12.34 EUR), read back as
4
+ * `Money<C>`. Exact up to SQLite's limit of ±9.2·10^18 minor units. better-sqlite3 returns integers
5
+ * as numbers by default, which are exact only below 2^53; beyond that reading throws, so call
6
+ * `defaultSafeIntegers(true)` on the database for larger amounts. Writing another currency, or a
7
+ * value at a scale other than the currency's, throws.
8
+ *
9
+ * total: moneyMinor('EUR')('total_minor').notNull()
10
+ */
11
+ export declare function moneyMinor<const C extends CurrencyInput>(currency: C): {
12
+ (): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
13
+ name: "";
14
+ dataType: "custom";
15
+ columnType: "SQLiteCustomColumn";
16
+ data: Money<C>;
17
+ driverParam: number | bigint;
18
+ enumValues: undefined;
19
+ }>;
20
+ <TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
21
+ name: "";
22
+ dataType: "custom";
23
+ columnType: "SQLiteCustomColumn";
24
+ data: Money<C>;
25
+ driverParam: number | bigint;
26
+ enumValues: undefined;
27
+ }>;
28
+ <TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
29
+ name: TName;
30
+ dataType: "custom";
31
+ columnType: "SQLiteCustomColumn";
32
+ data: Money<C>;
33
+ driverParam: number | bigint;
34
+ enumValues: undefined;
35
+ }>;
36
+ };
37
+ /**
38
+ * A TEXT column holding the exact decimal string (`12.34`), read back as `Money<C>`. SQLite's
39
+ * NUMERIC affinity would store some decimals as REAL and lose digits, so they are kept as text.
40
+ * Writing another currency, or a value at a scale other than the currency's, throws.
41
+ *
42
+ * price: moneyDecimal('EUR')('price').notNull()
43
+ */
44
+ export declare function moneyDecimal<const C extends CurrencyInput>(currency: C): {
45
+ (): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
46
+ name: "";
47
+ dataType: "custom";
48
+ columnType: "SQLiteCustomColumn";
49
+ data: Money<C>;
50
+ driverParam: string;
51
+ enumValues: undefined;
52
+ }>;
53
+ <TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
54
+ name: "";
55
+ dataType: "custom";
56
+ columnType: "SQLiteCustomColumn";
57
+ data: Money<C>;
58
+ driverParam: string;
59
+ enumValues: undefined;
60
+ }>;
61
+ <TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
62
+ name: TName;
63
+ dataType: "custom";
64
+ columnType: "SQLiteCustomColumn";
65
+ data: Money<C>;
66
+ driverParam: string;
67
+ enumValues: undefined;
68
+ }>;
69
+ };
70
+ /**
71
+ * A TEXT column holding `{"amount":"12.34","currency":"EUR"}`, with the currency in each value.
72
+ * Pass a currency to fix it: then a value in another currency throws, on write and on read.
73
+ *
74
+ * refund: moneyJson()('refund')
75
+ */
76
+ export declare function moneyJson<const C extends CurrencyInput = CurrencyInput>(currency?: C): {
77
+ (): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
78
+ name: "";
79
+ dataType: "custom";
80
+ columnType: "SQLiteCustomColumn";
81
+ data: Money<C>;
82
+ driverParam: string;
83
+ enumValues: undefined;
84
+ }>;
85
+ <TConfig extends Record<string, any>>(fieldConfig?: TConfig | undefined): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
86
+ name: "";
87
+ dataType: "custom";
88
+ columnType: "SQLiteCustomColumn";
89
+ data: Money<C>;
90
+ driverParam: string;
91
+ enumValues: undefined;
92
+ }>;
93
+ <TName extends string>(dbName: TName, fieldConfig?: unknown): import("drizzle-orm/sqlite-core").SQLiteCustomColumnBuilder<{
94
+ name: TName;
95
+ dataType: "custom";
96
+ columnType: "SQLiteCustomColumn";
97
+ data: Money<C>;
98
+ driverParam: string;
99
+ enumValues: undefined;
100
+ }>;
101
+ };
102
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/drizzle/sqlite.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAUxD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,QAAQ,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAMpE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,QAAQ,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAStE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,aAAa,GAAG,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;EAMpF"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `@ducatjs/money/drizzle/sqlite`: Drizzle ORM column types for SQLite (better-sqlite3, libSQL,
3
+ * Bun). Selects return `Money`, inserts and updates accept it, and the currency is part of the
4
+ * column's type.
5
+ *
6
+ * import { sqliteTable } from 'drizzle-orm/sqlite-core';
7
+ * import { moneyMinor } from '@ducatjs/money/drizzle/sqlite';
8
+ *
9
+ * const orders = sqliteTable('orders', { total: moneyMinor('EUR')('total_minor').notNull() });
10
+ */
11
+ import { customType } from 'drizzle-orm/sqlite-core';
12
+ import { currencyExponent } from '../currency.js';
13
+ import { decimalFromDriver, decimalToDriver, jsonFromDriver, jsonToDriver, minorFromDriver, minorToDriver, } from '../sql.js';
14
+ /**
15
+ * An INTEGER column of minor units in a fixed currency (`1234` is 12.34 EUR), read back as
16
+ * `Money<C>`. Exact up to SQLite's limit of ±9.2·10^18 minor units. better-sqlite3 returns integers
17
+ * as numbers by default, which are exact only below 2^53; beyond that reading throws, so call
18
+ * `defaultSafeIntegers(true)` on the database for larger amounts. Writing another currency, or a
19
+ * value at a scale other than the currency's, throws.
20
+ *
21
+ * total: moneyMinor('EUR')('total_minor').notNull()
22
+ */
23
+ export function moneyMinor(currency) {
24
+ return customType({
25
+ dataType: () => 'integer',
26
+ toDriver: (money) => minorToDriver(money, currency),
27
+ fromDriver: (value) => minorFromDriver(value, currency),
28
+ });
29
+ }
30
+ /**
31
+ * A TEXT column holding the exact decimal string (`12.34`), read back as `Money<C>`. SQLite's
32
+ * NUMERIC affinity would store some decimals as REAL and lose digits, so they are kept as text.
33
+ * Writing another currency, or a value at a scale other than the currency's, throws.
34
+ *
35
+ * price: moneyDecimal('EUR')('price').notNull()
36
+ */
37
+ export function moneyDecimal(currency) {
38
+ // fail at the schema definition, not on the first query, for an unknown currency
39
+ currencyExponent(currency);
40
+ return customType({
41
+ dataType: () => 'text',
42
+ toDriver: (money) => decimalToDriver(money, currency, undefined),
43
+ fromDriver: (value) => decimalFromDriver(value, currency),
44
+ });
45
+ }
46
+ /**
47
+ * A TEXT column holding `{"amount":"12.34","currency":"EUR"}`, with the currency in each value.
48
+ * Pass a currency to fix it: then a value in another currency throws, on write and on read.
49
+ *
50
+ * refund: moneyJson()('refund')
51
+ */
52
+ export function moneyJson(currency) {
53
+ return customType({
54
+ dataType: () => 'text',
55
+ toDriver: (money) => jsonToDriver(money, currency),
56
+ fromDriver: (value) => jsonFromDriver(value, currency),
57
+ });
58
+ }
59
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/drizzle/sqlite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAgC,QAAW;IACnE,OAAO,UAAU,CAAkD;QACjE,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS;QACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC;QACnD,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC;KACxD,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAgC,QAAW;IACrE,iFAAiF;IACjF,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAE3B,OAAO,UAAU,CAAyC;QACxD,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM;QACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC;QAChE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC;KAC1D,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAgD,QAAY;IACnF,OAAO,UAAU,CAAyC;QACxD,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM;QACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC;QAClD,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC;KACvD,CAAC,CAAC;AACL,CAAC"}
package/dist/ecb.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { type RateTable } from './provider.js';
2
+ /**
3
+ * Parses eurofxref-daily.xml, eurofxref-hist-90d.xml or eurofxref-hist.xml into one EUR-based table
4
+ * per day, newest first. Each rate is EUR → currency, with `source: 'ECB'`, `type: 'reference'` and
5
+ * `asOf` the publication day. A day without any rates is left out, so a lookback goes on to the day
6
+ * before. A rate entry that can't be read throws SyntaxError.
7
+ *
8
+ * const [latest] = parseEcbXml(await readFile('eurofxref-daily.xml', 'utf8'));
9
+ */
10
+ export declare function parseEcbXml(xml: string): RateTable[];
11
+ /**
12
+ * Parses eurofxref.csv or eurofxref-hist.csv (from the ECB's zips) the way parseEcbXml does.
13
+ * 'N/A' and blank cells (currencies not quoted that day) are listed in the table's `missing`, and a
14
+ * day with no rates at all is left out, so a lookback goes on to the day before.
15
+ */
16
+ export declare function parseEcbCsv(csv: string): RateTable[];
17
+ /** Options for parseEcbSdmxCsv. */
18
+ export interface EcbSdmxOptions {
19
+ /**
20
+ * The decimals each currency is published with, for a file without the DECIMALS column (as
21
+ * fetched with `detail=dataonly`). A DECIMALS column in the file takes precedence.
22
+ */
23
+ readonly decimals?: Readonly<Record<string, number>>;
24
+ }
25
+ /**
26
+ * The DECIMALS of each series in an ECB SDMX-CSV file: the data file itself with full detail, or
27
+ * the series file the API returns with `detail=nodata`.
28
+ */
29
+ export declare function parseEcbSdmxDecimals(csv: string): Record<string, number>;
30
+ /**
31
+ * Parses the ECB Data Portal's SDMX-CSV (`format=csvdata`) for the daily euro reference rates
32
+ * (dataflow EXR, key `D.<currencies>.EUR.SP00.A`) into one EUR-based table per day, newest first,
33
+ * like parseEcbXml. Any date range back to 1999 comes in one file.
34
+ *
35
+ * The API strips trailing zeros ('136.8' for the published 136.80), so the published digits are
36
+ * rebuilt from each series' DECIMALS attribute: in the file with full detail, or from `decimals`
37
+ * (see parseEcbSdmxDecimals) for a `detail=dataonly` file. Two series overstate it (THB and LTL),
38
+ * and are padded only to the decimals the ECB actually published. Without DECIMALS, the digits
39
+ * stay as the file gives them; the values are exact either way. An empty or `NaN` value is a missing value.
40
+ * A row for another frequency, rate type or base currency, or with a UNIT_MULT other than 0,
41
+ * throws SyntaxError rather than being read as a daily rate. Discontinued series simply end, so
42
+ * the tables of their last days include them.
43
+ *
44
+ * No `availableAt` is set, as with parseEcbXml: the API gives no publication time, and the ECB
45
+ * publishes at about 16:00 CET. So a backtest that asks for 10:00 CET on a day still gets that
46
+ * day's rate; ask for the day before if that matters.
47
+ *
48
+ * const tables = parseEcbSdmxCsv(await (await fetch(url)).text());
49
+ * const history = rateHistory(tables); // from @ducatjs/money/convert/history
50
+ */
51
+ export declare function parseEcbSdmxCsv(csv: string, options?: EcbSdmxOptions): RateTable[];
52
+ //# sourceMappingURL=ecb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecb.d.ts","sourceRoot":"","sources":["../src/ecb.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAoE1D;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CAgBpD;AAiCD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CA+BpD;AASD,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AA+CD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxE;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,SAAS,EAAE,CAwDlF"}
package/dist/ecb.js ADDED
@@ -0,0 +1,285 @@
1
+ // Offline parsers for the ECB euro foreign exchange reference rates (eurofxref). No I/O: fetch or
2
+ // read the files yourself, or use ducat/convert/fetch. Rates are kept exactly as published.
3
+ import { rateTable } from './provider.js';
4
+ import { addDayRate, csvRows, newestFirst, readRateCell, withDecimals } from './rate-text.js';
5
+ const MONTHS = [
6
+ 'january',
7
+ 'february',
8
+ 'march',
9
+ 'april',
10
+ 'may',
11
+ 'june',
12
+ 'july',
13
+ 'august',
14
+ 'september',
15
+ 'october',
16
+ 'november',
17
+ 'december',
18
+ ];
19
+ const DAY_CUBE = /<Cube\s+time=["'](\d{4}-\d\d-\d\d)["']\s*>([\s\S]*?)<\/Cube>/g;
20
+ // One <Cube .../> element, and its attributes in any order and with any quoting or spacing.
21
+ const INNER_CUBE = /<Cube\b([^>]*?)\/?>/g;
22
+ const ATTRIBUTE = /([A-Za-z]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
23
+ const RATE_TEXT = /^\d+(?:\.\d+)?$/;
24
+ const ISO_DAY = /^(\d{4})-(\d\d)-(\d\d)$/;
25
+ /**
26
+ * The ECB's rates are typed 'reference', not 'mid': they are set once a day, around 14:10 CET, from
27
+ * a regular concertation between central banks, and published for information only. They are not a
28
+ * market mid-rate that anyone deals at.
29
+ */
30
+ function ecbTable(asOf, rates) {
31
+ return rateTable({ base: 'EUR', asOf, source: 'ECB', type: 'reference', rates });
32
+ }
33
+ function notAnEcbFile() {
34
+ throw new SyntaxError('Not an ECB reference rate file');
35
+ }
36
+ function withoutByteOrderMark(text) {
37
+ return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
38
+ }
39
+ /** The currency → rate attributes of the <Cube> elements in one day's cube. */
40
+ function ratesOfDay(body) {
41
+ const rates = {};
42
+ for (const [, attributeText] of body.matchAll(INNER_CUBE)) {
43
+ const attributes = {};
44
+ for (const [, name, doubleQuoted, singleQuoted] of attributeText.matchAll(ATTRIBUTE)) {
45
+ attributes[name] = doubleQuoted ?? singleQuoted;
46
+ }
47
+ const currency = attributes['currency'];
48
+ const rate = attributes['rate'];
49
+ if (currency === undefined && rate === undefined) {
50
+ continue;
51
+ }
52
+ // Fail rather than silently drop a currency: a missing rate would look like "not quoted".
53
+ if (currency === undefined ||
54
+ !/^[A-Z]{3}$/.test(currency) ||
55
+ rate === undefined ||
56
+ !RATE_TEXT.test(rate)) {
57
+ throw new SyntaxError(`Malformed ECB rate entry <Cube${attributeText}>`);
58
+ }
59
+ rates[currency] = rate;
60
+ }
61
+ return rates;
62
+ }
63
+ /**
64
+ * Parses eurofxref-daily.xml, eurofxref-hist-90d.xml or eurofxref-hist.xml into one EUR-based table
65
+ * per day, newest first. Each rate is EUR → currency, with `source: 'ECB'`, `type: 'reference'` and
66
+ * `asOf` the publication day. A day without any rates is left out, so a lookback goes on to the day
67
+ * before. A rate entry that can't be read throws SyntaxError.
68
+ *
69
+ * const [latest] = parseEcbXml(await readFile('eurofxref-daily.xml', 'utf8'));
70
+ */
71
+ export function parseEcbXml(xml) {
72
+ let days = 0;
73
+ const tables = [];
74
+ for (const [, asOf, body] of withoutByteOrderMark(xml).matchAll(DAY_CUBE)) {
75
+ days++;
76
+ const day = isoDay(asOf);
77
+ const rates = ratesOfDay(body);
78
+ if (Object.keys(rates).length > 0) {
79
+ tables.push(ecbTable(day, rates));
80
+ }
81
+ }
82
+ if (days === 0) {
83
+ notAnEcbFile();
84
+ }
85
+ return newestFirst(tables);
86
+ }
87
+ /** The ISO date of year-month-day, or a SyntaxError when that day doesn't exist (32 November). */
88
+ function calendarDay(year, month, day) {
89
+ const date = new Date(Date.UTC(year, month - 1, day));
90
+ if (date.getUTCFullYear() !== year ||
91
+ date.getUTCMonth() !== month - 1 ||
92
+ date.getUTCDate() !== day) {
93
+ throw new SyntaxError(`Not a calendar day: ${year}-${month}-${day}`);
94
+ }
95
+ return date.toISOString().slice(0, 10);
96
+ }
97
+ /** '2026-11-06', or '06 November 2026' as in the daily CSV, as an ISO date. */
98
+ function isoDay(text) {
99
+ const iso = ISO_DAY.exec(text);
100
+ if (iso !== null) {
101
+ return calendarDay(Number(iso[1]), Number(iso[2]), Number(iso[3]));
102
+ }
103
+ const match = /^(\d{1,2}) ([A-Za-z]+) (\d{4})$/.exec(text);
104
+ if (match === null) {
105
+ notAnEcbFile();
106
+ }
107
+ const [, day, monthName, year] = match;
108
+ const month = MONTHS.indexOf(monthName.toLowerCase()) + 1;
109
+ if (month === 0) {
110
+ notAnEcbFile();
111
+ }
112
+ return calendarDay(Number(year), month, Number(day));
113
+ }
114
+ /**
115
+ * Parses eurofxref.csv or eurofxref-hist.csv (from the ECB's zips) the way parseEcbXml does.
116
+ * 'N/A' and blank cells (currencies not quoted that day) are listed in the table's `missing`, and a
117
+ * day with no rates at all is left out, so a lookback goes on to the day before.
118
+ */
119
+ export function parseEcbCsv(csv) {
120
+ const lines = withoutByteOrderMark(csv)
121
+ .split(/\r?\n/)
122
+ .filter((line) => line.trim() !== '');
123
+ const columns = (lines[0] ?? '').split(',').map((cell) => cell.trim());
124
+ if (columns[0] !== 'Date') {
125
+ notAnEcbFile();
126
+ }
127
+ const tables = [];
128
+ for (const line of lines.slice(1)) {
129
+ const cells = line.split(',').map((cell) => cell.trim());
130
+ const day = isoDay(cells[0]);
131
+ const rates = {};
132
+ let quoted = 0;
133
+ for (let i = 1; i < columns.length; i++) {
134
+ const currency = columns[i];
135
+ if (!currency) {
136
+ continue;
137
+ }
138
+ const rate = readRateCell(cells[i] ?? '', `${currency} on ${day}`);
139
+ rates[currency] = rate;
140
+ if (rate !== null) {
141
+ quoted++;
142
+ }
143
+ }
144
+ if (quoted > 0) {
145
+ tables.push(ecbTable(day, rates));
146
+ }
147
+ }
148
+ return newestFirst(tables);
149
+ }
150
+ // Series whose DECIMALS attribute says more than the ECB has ever published for them. Evidence:
151
+ // eurofxref-daily.xml publishes THB to 3 decimals (37.459 on 2026-11-06) while DECIMALS says 4,
152
+ // and eurofxref-hist.xml (1999 to 2026) never has more than 4 for LTL, whose DECIMALS says 5.
153
+ // Padding to the attribute would invent a digit. A cap only limits padding: a value published
154
+ // with more digits keeps them all (withDecimals never drops digits).
155
+ const PUBLISHED_DECIMALS = { THB: 3, LTL: 4 };
156
+ /** A DECIMALS cell as a number of decimals, or SyntaxError. */
157
+ function readDecimals(text, where) {
158
+ if (!/^\d{1,2}$/.test(text) || Number(text) > 30) {
159
+ throw new SyntaxError(`Malformed DECIMALS ${JSON.stringify(text)} (${where})`);
160
+ }
161
+ return Number(text);
162
+ }
163
+ /** A series' stated decimals, capped at what the ECB actually publishes for it. */
164
+ function statedDecimals(currency, text) {
165
+ const stated = readDecimals(text, currency);
166
+ const published = PUBLISHED_DECIMALS[currency];
167
+ if (published !== undefined && published < stated) {
168
+ return published;
169
+ }
170
+ return stated;
171
+ }
172
+ /** The records of an SDMX-CSV file after its header, each with as many cells as the header. */
173
+ function sdmxRecords(csv) {
174
+ const [header, ...rows] = csvRows(withoutByteOrderMark(csv));
175
+ if (header === undefined) {
176
+ throw new SyntaxError('Not an ECB SDMX-CSV file: it is empty');
177
+ }
178
+ for (const cells of rows) {
179
+ if (cells.length !== header.length) {
180
+ throw new SyntaxError(`Malformed ECB SDMX-CSV row: ${cells.length} cells, the header has ${header.length}`);
181
+ }
182
+ }
183
+ return { header, rows };
184
+ }
185
+ /** The columns of an SDMX-CSV file, by name, or SyntaxError when a required one is missing. */
186
+ function sdmxColumns(header, required) {
187
+ const columns = new Map(header.map((name, i) => [name.trim(), i]));
188
+ for (const name of required) {
189
+ if (!columns.has(name)) {
190
+ throw new SyntaxError(`Not an ECB SDMX-CSV file: no ${name} column`);
191
+ }
192
+ }
193
+ return columns;
194
+ }
195
+ /**
196
+ * The DECIMALS of each series in an ECB SDMX-CSV file: the data file itself with full detail, or
197
+ * the series file the API returns with `detail=nodata`.
198
+ */
199
+ export function parseEcbSdmxDecimals(csv) {
200
+ const { header, rows } = sdmxRecords(csv);
201
+ const columns = sdmxColumns(header, ['CURRENCY', 'DECIMALS']);
202
+ const decimals = {};
203
+ for (const cells of rows) {
204
+ const currency = cells[columns.get('CURRENCY')].trim();
205
+ decimals[currency] = statedDecimals(currency, cells[columns.get('DECIMALS')].trim());
206
+ }
207
+ return decimals;
208
+ }
209
+ /** The value of an optional column, or undefined when the file doesn't have it. */
210
+ function optionalCell(cells, columns, name) {
211
+ const index = columns.get(name);
212
+ if (index === undefined) {
213
+ return undefined;
214
+ }
215
+ return cells[index]?.trim();
216
+ }
217
+ /**
218
+ * Parses the ECB Data Portal's SDMX-CSV (`format=csvdata`) for the daily euro reference rates
219
+ * (dataflow EXR, key `D.<currencies>.EUR.SP00.A`) into one EUR-based table per day, newest first,
220
+ * like parseEcbXml. Any date range back to 1999 comes in one file.
221
+ *
222
+ * The API strips trailing zeros ('136.8' for the published 136.80), so the published digits are
223
+ * rebuilt from each series' DECIMALS attribute: in the file with full detail, or from `decimals`
224
+ * (see parseEcbSdmxDecimals) for a `detail=dataonly` file. Two series overstate it (THB and LTL),
225
+ * and are padded only to the decimals the ECB actually published. Without DECIMALS, the digits
226
+ * stay as the file gives them; the values are exact either way. An empty or `NaN` value is a missing value.
227
+ * A row for another frequency, rate type or base currency, or with a UNIT_MULT other than 0,
228
+ * throws SyntaxError rather than being read as a daily rate. Discontinued series simply end, so
229
+ * the tables of their last days include them.
230
+ *
231
+ * No `availableAt` is set, as with parseEcbXml: the API gives no publication time, and the ECB
232
+ * publishes at about 16:00 CET. So a backtest that asks for 10:00 CET on a day still gets that
233
+ * day's rate; ask for the day before if that matters.
234
+ *
235
+ * const tables = parseEcbSdmxCsv(await (await fetch(url)).text());
236
+ * const history = rateHistory(tables); // from @ducatjs/money/convert/history
237
+ */
238
+ export function parseEcbSdmxCsv(csv, options) {
239
+ const { header, rows } = sdmxRecords(csv);
240
+ const columns = sdmxColumns(header, ['CURRENCY', 'CURRENCY_DENOM', 'TIME_PERIOD', 'OBS_VALUE']);
241
+ const at = (cells, name) => cells[columns.get(name)]?.trim() ?? '';
242
+ const days = new Map();
243
+ for (const cells of rows) {
244
+ const currency = at(cells, 'CURRENCY');
245
+ const day = at(cells, 'TIME_PERIOD');
246
+ const where = `${currency} on ${day}`;
247
+ const unexpected = at(cells, 'CURRENCY_DENOM') !== 'EUR' ||
248
+ (optionalCell(cells, columns, 'FREQ') ?? 'D') !== 'D' ||
249
+ (optionalCell(cells, columns, 'EXR_TYPE') ?? 'SP00') !== 'SP00' ||
250
+ (optionalCell(cells, columns, 'EXR_SUFFIX') ?? 'A') !== 'A';
251
+ if (unexpected) {
252
+ throw new SyntaxError(`Not a daily EUR reference rate: ${cells.join(',')}`);
253
+ }
254
+ if (!/^[A-Z]{3}$/.test(currency) || !ISO_DAY.test(day)) {
255
+ throw new SyntaxError(`Malformed ECB SDMX-CSV row: ${cells.join(',')}`);
256
+ }
257
+ const multiplier = optionalCell(cells, columns, 'UNIT_MULT');
258
+ if (multiplier !== undefined && multiplier !== '' && multiplier !== '0') {
259
+ throw new SyntaxError(`Unsupported UNIT_MULT ${multiplier} (${where})`);
260
+ }
261
+ const value = at(cells, 'OBS_VALUE');
262
+ let rate = readRateCell(value, where);
263
+ if (rate !== null) {
264
+ // The file's own DECIMALS, capped like parseEcbSdmxDecimals; else the caller's, as given.
265
+ const stated = optionalCell(cells, columns, 'DECIMALS');
266
+ let decimals = options?.decimals?.[currency];
267
+ if (stated !== undefined && stated !== '') {
268
+ decimals = statedDecimals(currency, stated);
269
+ }
270
+ if (decimals !== undefined) {
271
+ rate = withDecimals(rate, decimals);
272
+ }
273
+ }
274
+ const isoDay = calendarDay(Number(day.slice(0, 4)), Number(day.slice(5, 7)), Number(day.slice(8)));
275
+ addDayRate(days, isoDay, currency, rate);
276
+ }
277
+ const tables = [];
278
+ for (const [day, rates] of days) {
279
+ if (Object.values(rates).some((rate) => rate !== null)) {
280
+ tables.push(ecbTable(day, rates));
281
+ }
282
+ }
283
+ return newestFirst(tables);
284
+ }
285
+ //# sourceMappingURL=ecb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecb.js","sourceRoot":"","sources":["../src/ecb.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { type Factor } from './decimal.js';
2
+ import { type Money } from './money.js';
3
+ import type { Rate } from './rate.js';
4
+ import { type RoundingMode } from './rounding.js';
5
+ /**
6
+ * A rounded value and what rounding left out: `value + residual` is exactly the unrounded amount.
7
+ */
8
+ export interface Rounded<C extends string> {
9
+ readonly value: Money<C>;
10
+ readonly residual: ExactMoney<C>;
11
+ }
12
+ /**
13
+ * An exact amount of `currency`: num/den major units, reduced, den > 0. Build it with `exact()`.
14
+ */
15
+ export declare class ExactMoney<C extends string = string> {
16
+ readonly currency: C;
17
+ readonly num: bigint;
18
+ readonly den: bigint;
19
+ /** @internal use exact() */
20
+ constructor(currency: C, numerator: bigint, denominator: bigint);
21
+ /** this + other, exactly. */
22
+ add(other: ExactMoney<C>): ExactMoney<C>;
23
+ /** this − other, exactly. */
24
+ subtract(other: ExactMoney<C>): ExactMoney<C>;
25
+ /** −this. */
26
+ negate(): ExactMoney<C>;
27
+ /** this × factor, exactly. */
28
+ multiply(factor: Factor): ExactMoney<C>;
29
+ /** this ÷ divisor, exactly. */
30
+ divide(divisor: Factor): ExactMoney<C>;
31
+ /** Converts exactly at `rate`; nothing is rounded. */
32
+ convert<To extends string>(rate: Rate<C, To>): ExactMoney<To>;
33
+ /** −1, 0 or 1. */
34
+ compare(other: ExactMoney<C>): -1 | 0 | 1;
35
+ /** Same currency and the same exact value. */
36
+ equals(other: ExactMoney): boolean;
37
+ /** −1, 0 or 1. */
38
+ sign(): -1 | 0 | 1;
39
+ /** Whether the amount is zero. */
40
+ isZero(): boolean;
41
+ /** Whether the amount fits `scale` (default: the currency's exponent) without rounding. */
42
+ isExact(scale?: number): boolean;
43
+ /** Rounds once to `scale` (default: the currency's exponent). */
44
+ round(rounding?: RoundingMode, scale?: number): Money<C>;
45
+ /** Rounds once and reports the difference, for booking it (e.g. an "FX rounding" ledger line). */
46
+ roundWithResidual(rounding?: RoundingMode, scale?: number): Rounded<C>;
47
+ /** "10/3 EUR", for debugging only. */
48
+ toString(): string;
49
+ /** Blocks `a + b` and `a > b`: round() first. */
50
+ [Symbol.toPrimitive](hint: string): string;
51
+ }
52
+ /**
53
+ * The exact value of `money`, for chained arithmetic: `exact(price).divide(3).round('halfEven')`.
54
+ */
55
+ export declare function exact<C extends string>(money: Money<C>): ExactMoney<C>;
56
+ /**
57
+ * The exact sum (zero of `currency` for an empty list). Round it once for a batch's rounding line.
58
+ */
59
+ export declare function sumExact<C extends string>(items: readonly ExactMoney<C>[], currency?: C): ExactMoney<C>;
60
+ /** Whether `value` is an ExactMoney. */
61
+ export declare function isExactMoney(value: unknown): value is ExactMoney;
62
+ //# sourceMappingURL=exact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact.d.ts","sourceRoot":"","sources":["../src/exact.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAA+B,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAA0C,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAE1F;;GAEG;AACH,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM;IACvC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAC/C,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,4BAA4B;gBAChB,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;IAO/D,6BAA6B;IAC7B,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAMxC,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAI7C,aAAa;IACb,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC;IAIvB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC;IAKvC,+BAA+B;IAC/B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC;IAQtC,sDAAsD;IACtD,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC;IAO7D,kBAAkB;IAClB,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAczC,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;IAIlC,kBAAkB;IAClB,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAUlB,kCAAkC;IAClC,MAAM,IAAI,OAAO;IAIjB,2FAA2F;IAC3F,OAAO,CAAC,KAAK,GAAE,MAAwC,GAAG,OAAO;IAIjE,iEAAiE;IACjE,KAAK,CACH,QAAQ,GAAE,YAA+B,EACzC,KAAK,GAAE,MAAwC,GAC9C,KAAK,CAAC,CAAC,CAAC;IAKX,kGAAkG;IAClG,iBAAiB,CAAC,QAAQ,GAAE,YAA+B,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAMxF,sCAAsC;IACtC,QAAQ,IAAI,MAAM;IAOlB,iDAAiD;IACjD,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAM3C;AAQD;;GAEG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAEtE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,MAAM,EACvC,KAAK,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,EAC/B,QAAQ,CAAC,EAAE,CAAC,GACX,UAAU,CAAC,CAAC,CAAC,CAUf;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE"}