rune-lab 0.3.1 → 0.4.1

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 (397) hide show
  1. package/.gitignore +12 -0
  2. package/README.md +4 -4
  3. package/deno.json +50 -0
  4. package/justfile +5 -0
  5. package/package.json +3 -80
  6. package/scripts/build_pkg.ts +51 -0
  7. package/scripts/ci.just +26 -0
  8. package/scripts/deploy.just +21 -0
  9. package/scripts/dev.just +4 -0
  10. package/scripts/inject.just +72 -0
  11. package/src/RuneProvider.svelte +105 -0
  12. package/src/i18n/message-resolver.test.ts +103 -0
  13. package/src/i18n/message-resolver.ts +89 -0
  14. package/src/i18n/project.inlang/settings.json +26 -0
  15. package/src/i18n/translations/ar.json +89 -0
  16. package/src/i18n/translations/de.json +89 -0
  17. package/src/i18n/translations/en.json +89 -0
  18. package/src/i18n/translations/es.json +89 -0
  19. package/src/i18n/translations/fr.json +89 -0
  20. package/src/i18n/translations/hi.json +89 -0
  21. package/src/i18n/translations/it.json +89 -0
  22. package/src/i18n/translations/ja.json +89 -0
  23. package/src/i18n/translations/ko.json +89 -0
  24. package/src/i18n/translations/pt.json +89 -0
  25. package/src/i18n/translations/ru.json +89 -0
  26. package/src/i18n/translations/vi.json +89 -0
  27. package/src/i18n/translations/zh.json +89 -0
  28. package/src/kernel/deno.json +5 -0
  29. package/src/kernel/src/actions/portal.ts +41 -0
  30. package/src/kernel/src/actions/shortcut-listener.ts +50 -0
  31. package/src/kernel/src/context/app.svelte.ts +98 -0
  32. package/src/kernel/src/context/context.ts +31 -0
  33. package/src/kernel/src/context/stores.svelte.ts +96 -0
  34. package/src/kernel/src/context/types.ts +110 -0
  35. package/src/kernel/src/context/useRuneLab.ts +38 -0
  36. package/src/kernel/src/mod.ts +13 -0
  37. package/src/kernel/src/persistence/createConfigStore.svelte.ts +120 -0
  38. package/src/kernel/src/persistence/drivers.test.ts +95 -0
  39. package/src/kernel/src/persistence/drivers.ts +77 -0
  40. package/src/kernel/src/persistence/provider.test.ts +58 -0
  41. package/src/kernel/src/persistence/provider.ts +58 -0
  42. package/src/kernel/src/persistence/types.ts +19 -0
  43. package/src/kernel/src/persistence/usePersistence.ts +9 -0
  44. package/src/kernel/src/registry/mod.ts +221 -0
  45. package/src/kernel/src/registry/registry.test.ts +162 -0
  46. package/src/kernel/src/tokens/props.ts +94 -0
  47. package/src/mod.ts +10 -0
  48. package/src/runes/layout/deno.json +5 -0
  49. package/src/runes/layout/src/APP_CONFIGURATIONS.ts +56 -0
  50. package/{dist/ui/features/config → src/runes/layout/src}/AppSettingSelector.svelte +1 -1
  51. package/{dist/ui/layout → src/runes/layout/src}/ConnectedNavigationPanel.svelte +7 -8
  52. package/{dist/ui/layout → src/runes/layout/src}/ConnectedWorkspaceStrip.svelte +5 -3
  53. package/src/runes/layout/src/Icon.svelte +7 -0
  54. package/src/runes/layout/src/LanguageSelector.svelte +45 -0
  55. package/{dist/ui/layout → src/runes/layout/src}/NavigationPanel.svelte +1 -4
  56. package/src/runes/layout/src/ResourceSelector.svelte +90 -0
  57. package/src/runes/layout/src/ThemeSelector.svelte +50 -0
  58. package/{dist/ui/layout → src/runes/layout/src}/WorkspaceLayout.svelte +13 -3
  59. package/{dist/ui/layout → src/runes/layout/src}/WorkspaceStrip.svelte +1 -1
  60. package/src/runes/layout/src/connection-factory.ts +95 -0
  61. package/src/runes/layout/src/language.svelte.ts +44 -0
  62. package/src/runes/layout/src/mod.ts +71 -0
  63. package/src/runes/layout/src/store.svelte.ts +142 -0
  64. package/src/runes/layout/src/theme.svelte.ts +73 -0
  65. package/src/runes/layout/src/types.ts +11 -0
  66. package/src/runes/palettes/deno.json +8 -0
  67. package/{dist/ui/features/command-palette → src/runes/palettes/src/commands}/CommandPalette.svelte +4 -4
  68. package/src/runes/palettes/src/commands/mod.ts +2 -0
  69. package/src/runes/palettes/src/commands/store.svelte.ts +80 -0
  70. package/src/runes/palettes/src/mod.ts +51 -0
  71. package/{dist/ui/components → src/runes/palettes/src/notifications}/Toaster.svelte +1 -2
  72. package/src/runes/palettes/src/notifications/bridge.ts +56 -0
  73. package/src/runes/palettes/src/notifications/mod.ts +4 -0
  74. package/src/runes/palettes/src/notifications/store.svelte.ts +58 -0
  75. package/{dist/ui/features → src/runes/palettes/src}/shortcuts/ShortcutPalette.svelte +4 -4
  76. package/src/runes/palettes/src/shortcuts/mod.ts +4 -0
  77. package/src/runes/palettes/src/shortcuts/store.svelte.ts +85 -0
  78. package/src/runes/palettes/src/shortcuts/types.ts +4 -0
  79. package/src/runes/palettes/src/shortcuts/useShortcuts.ts +85 -0
  80. package/src/runes/plugins/money/deno.json +8 -0
  81. package/src/runes/plugins/money/src/CurrencySelector.svelte +41 -0
  82. package/src/runes/plugins/money/src/MoneyDisplay.svelte +142 -0
  83. package/src/runes/plugins/money/src/MoneyInput.svelte +208 -0
  84. package/src/runes/plugins/money/src/currency.svelte.ts +95 -0
  85. package/src/runes/plugins/money/src/currency.test.ts +40 -0
  86. package/src/runes/plugins/money/src/exchange-rate.svelte.ts +176 -0
  87. package/src/runes/plugins/money/src/exchange-rate.test.ts +95 -0
  88. package/src/runes/plugins/money/src/mod.ts +79 -0
  89. package/src/runes/plugins/money/src/money-primitive.test.ts +217 -0
  90. package/src/runes/plugins/money/src/money-primitive.ts +220 -0
  91. package/src/runes/plugins/money/src/money.test.ts +183 -0
  92. package/src/runes/plugins/money/src/money.ts +382 -0
  93. package/src/runes/plugins/money/src/strategies.test.ts +143 -0
  94. package/src/runes/plugins/money/src/strategies.ts +95 -0
  95. package/src/runes/plugins/money/src/types.ts +27 -0
  96. package/src/runes/plugins/money/src/useMoney.ts +261 -0
  97. package/src/runes/plugins/money/src/useMoneyFilter.ts +132 -0
  98. package/tsconfig.json +41 -0
  99. package/vite.config.ts +44 -0
  100. package/dist/core/index.d.ts +0 -4
  101. package/dist/core/index.d.ts.map +0 -1
  102. package/dist/core/index.js +0 -3
  103. package/dist/core/internal/message-resolver.d.ts +0 -42
  104. package/dist/core/internal/message-resolver.d.ts.map +0 -1
  105. package/dist/core/internal/message-resolver.js +0 -47
  106. package/dist/core/money/index.d.ts +0 -2
  107. package/dist/core/money/index.d.ts.map +0 -1
  108. package/dist/core/money/index.js +0 -2
  109. package/dist/core/money/money.d.ts +0 -57
  110. package/dist/core/money/money.d.ts.map +0 -1
  111. package/dist/core/money/money.js +0 -132
  112. package/dist/core/persistence/types.d.ts +0 -18
  113. package/dist/core/persistence/types.d.ts.map +0 -1
  114. package/dist/core/persistence/types.js +0 -2
  115. package/dist/index.d.ts +0 -5
  116. package/dist/index.js +0 -5
  117. package/dist/state/api.svelte.d.ts +0 -26
  118. package/dist/state/api.svelte.js +0 -65
  119. package/dist/state/app.svelte.d.ts +0 -48
  120. package/dist/state/app.svelte.js +0 -82
  121. package/dist/state/auth/index.d.ts +0 -2
  122. package/dist/state/auth/index.js +0 -2
  123. package/dist/state/auth/session.svelte.d.ts +0 -40
  124. package/dist/state/auth/session.svelte.js +0 -57
  125. package/dist/state/auth/types.d.ts +0 -30
  126. package/dist/state/auth/types.js +0 -3
  127. package/dist/state/cart.svelte.d.ts +0 -64
  128. package/dist/state/cart.svelte.js +0 -122
  129. package/dist/state/commands.svelte.d.ts +0 -46
  130. package/dist/state/commands.svelte.js +0 -62
  131. package/dist/state/composables/useMoney.d.ts +0 -15
  132. package/dist/state/composables/useMoney.js +0 -62
  133. package/dist/state/composables/usePersistence.d.ts +0 -2
  134. package/dist/state/composables/usePersistence.js +0 -5
  135. package/dist/state/composables/useRuneLab.d.ts +0 -19
  136. package/dist/state/composables/useRuneLab.js +0 -30
  137. package/dist/state/context.d.ts +0 -14
  138. package/dist/state/context.js +0 -15
  139. package/dist/state/createConfigStore.svelte.d.ts +0 -55
  140. package/dist/state/createConfigStore.svelte.js +0 -61
  141. package/dist/state/currency.svelte.d.ts +0 -28
  142. package/dist/state/currency.svelte.js +0 -80
  143. package/dist/state/daisyui.d.ts +0 -4
  144. package/dist/state/index.d.ts +0 -19
  145. package/dist/state/index.js +0 -25
  146. package/dist/state/language.svelte.d.ts +0 -64
  147. package/dist/state/language.svelte.js +0 -44
  148. package/dist/state/layout.svelte.d.ts +0 -49
  149. package/dist/state/layout.svelte.js +0 -108
  150. package/dist/state/persistence/drivers.d.ts +0 -12
  151. package/dist/state/persistence/drivers.js +0 -73
  152. package/dist/state/shortcuts.svelte.d.ts +0 -121
  153. package/dist/state/shortcuts.svelte.js +0 -222
  154. package/dist/state/theme.svelte.d.ts +0 -22
  155. package/dist/state/theme.svelte.js +0 -92
  156. package/dist/state/toast-bridge.d.ts +0 -29
  157. package/dist/state/toast-bridge.js +0 -43
  158. package/dist/state/toast.svelte.d.ts +0 -27
  159. package/dist/state/toast.svelte.js +0 -43
  160. package/dist/ui/actions/portal.d.ts +0 -7
  161. package/dist/ui/actions/portal.js +0 -32
  162. package/dist/ui/components/ApiMonitor.svelte +0 -162
  163. package/dist/ui/components/ApiMonitor.svelte.d.ts +0 -3
  164. package/dist/ui/components/Icon.svelte +0 -108
  165. package/dist/ui/components/Icon.svelte.d.ts +0 -14
  166. package/dist/ui/components/Kyntharil.svelte +0 -48
  167. package/dist/ui/components/Kyntharil.svelte.d.ts +0 -6
  168. package/dist/ui/components/RuneProvider.svelte +0 -198
  169. package/dist/ui/components/RuneProvider.svelte.d.ts +0 -42
  170. package/dist/ui/components/Toaster.svelte.d.ts +0 -18
  171. package/dist/ui/components/money/MoneyDisplay.svelte +0 -69
  172. package/dist/ui/components/money/MoneyDisplay.svelte.d.ts +0 -21
  173. package/dist/ui/components/money/MoneyDisplay.svelte.test.d.ts +0 -1
  174. package/dist/ui/components/money/MoneyDisplay.svelte.test.js +0 -74
  175. package/dist/ui/components/money/MoneyInput.svelte +0 -131
  176. package/dist/ui/components/money/MoneyInput.svelte.d.ts +0 -25
  177. package/dist/ui/components/money/index.d.ts +0 -2
  178. package/dist/ui/components/money/index.js +0 -3
  179. package/dist/ui/components/user/UserAvatar.svelte +0 -66
  180. package/dist/ui/components/user/UserAvatar.svelte.d.ts +0 -13
  181. package/dist/ui/components/user/UserProfile.svelte +0 -79
  182. package/dist/ui/components/user/UserProfile.svelte.d.ts +0 -17
  183. package/dist/ui/components/user/index.d.ts +0 -2
  184. package/dist/ui/components/user/index.js +0 -3
  185. package/dist/ui/features/command-palette/CommandPalette.svelte.d.ts +0 -6
  186. package/dist/ui/features/config/AppSettingSelector.svelte.d.ts +0 -13
  187. package/dist/ui/features/config/CurrencySelector.svelte +0 -67
  188. package/dist/ui/features/config/CurrencySelector.svelte.d.ts +0 -8
  189. package/dist/ui/features/config/LanguageSelector.svelte +0 -66
  190. package/dist/ui/features/config/LanguageSelector.svelte.d.ts +0 -8
  191. package/dist/ui/features/config/ThemeSelector.svelte +0 -73
  192. package/dist/ui/features/config/ThemeSelector.svelte.d.ts +0 -8
  193. package/dist/ui/features/notifications/NotificationBell.svelte.d.ts +0 -11
  194. package/dist/ui/features/notifications/index.d.ts +0 -1
  195. package/dist/ui/features/notifications/index.js +0 -2
  196. package/dist/ui/features/shortcuts/ShortcutPalette.svelte.d.ts +0 -6
  197. package/dist/ui/index.d.ts +0 -26
  198. package/dist/ui/index.js +0 -41
  199. package/dist/ui/layout/ConnectedNavigationPanel.svelte.d.ts +0 -10
  200. package/dist/ui/layout/ConnectedWorkspaceStrip.svelte.d.ts +0 -9
  201. package/dist/ui/layout/ContentArea.svelte.d.ts +0 -8
  202. package/dist/ui/layout/DetailPanel.svelte.d.ts +0 -7
  203. package/dist/ui/layout/NavigationPanel.svelte.d.ts +0 -14
  204. package/dist/ui/layout/WorkspaceLayout.svelte.d.ts +0 -25
  205. package/dist/ui/layout/WorkspaceStrip.svelte.d.ts +0 -11
  206. package/dist/ui/layout/index.d.ts +0 -7
  207. package/dist/ui/layout/index.js +0 -7
  208. package/dist/ui/paraglide/README.md +0 -94
  209. package/dist/ui/paraglide/messages/_index.d.ts +0 -87
  210. package/dist/ui/paraglide/messages/_index.js +0 -88
  211. package/dist/ui/paraglide/messages/abyss.d.ts +0 -16
  212. package/dist/ui/paraglide/messages/abyss.js +0 -84
  213. package/dist/ui/paraglide/messages/acid.d.ts +0 -16
  214. package/dist/ui/paraglide/messages/acid.js +0 -84
  215. package/dist/ui/paraglide/messages/active_toasts.d.ts +0 -16
  216. package/dist/ui/paraglide/messages/active_toasts.js +0 -84
  217. package/dist/ui/paraglide/messages/aed3.d.ts +0 -17
  218. package/dist/ui/paraglide/messages/aed3.js +0 -85
  219. package/dist/ui/paraglide/messages/all_currencies.d.ts +0 -16
  220. package/dist/ui/paraglide/messages/all_currencies.js +0 -84
  221. package/dist/ui/paraglide/messages/all_languages.d.ts +0 -16
  222. package/dist/ui/paraglide/messages/all_languages.js +0 -84
  223. package/dist/ui/paraglide/messages/all_themes.d.ts +0 -16
  224. package/dist/ui/paraglide/messages/all_themes.js +0 -84
  225. package/dist/ui/paraglide/messages/api_status.d.ts +0 -16
  226. package/dist/ui/paraglide/messages/api_status.js +0 -84
  227. package/dist/ui/paraglide/messages/app_info.d.ts +0 -16
  228. package/dist/ui/paraglide/messages/app_info.js +0 -84
  229. package/dist/ui/paraglide/messages/appearance.d.ts +0 -16
  230. package/dist/ui/paraglide/messages/appearance.js +0 -84
  231. package/dist/ui/paraglide/messages/aqua.d.ts +0 -16
  232. package/dist/ui/paraglide/messages/aqua.js +0 -84
  233. package/dist/ui/paraglide/messages/ar.d.ts +0 -16
  234. package/dist/ui/paraglide/messages/ar.js +0 -84
  235. package/dist/ui/paraglide/messages/autumn.d.ts +0 -16
  236. package/dist/ui/paraglide/messages/autumn.js +0 -84
  237. package/dist/ui/paraglide/messages/black.d.ts +0 -16
  238. package/dist/ui/paraglide/messages/black.js +0 -84
  239. package/dist/ui/paraglide/messages/brl3.d.ts +0 -17
  240. package/dist/ui/paraglide/messages/brl3.js +0 -85
  241. package/dist/ui/paraglide/messages/bumblebee.d.ts +0 -16
  242. package/dist/ui/paraglide/messages/bumblebee.js +0 -84
  243. package/dist/ui/paraglide/messages/business.d.ts +0 -16
  244. package/dist/ui/paraglide/messages/business.js +0 -84
  245. package/dist/ui/paraglide/messages/cad3.d.ts +0 -17
  246. package/dist/ui/paraglide/messages/cad3.js +0 -85
  247. package/dist/ui/paraglide/messages/caramellatte.d.ts +0 -16
  248. package/dist/ui/paraglide/messages/caramellatte.js +0 -84
  249. package/dist/ui/paraglide/messages/cmyk.d.ts +0 -16
  250. package/dist/ui/paraglide/messages/cmyk.js +0 -84
  251. package/dist/ui/paraglide/messages/cny3.d.ts +0 -17
  252. package/dist/ui/paraglide/messages/cny3.js +0 -85
  253. package/dist/ui/paraglide/messages/coffee.d.ts +0 -16
  254. package/dist/ui/paraglide/messages/coffee.js +0 -84
  255. package/dist/ui/paraglide/messages/commands_label.d.ts +0 -16
  256. package/dist/ui/paraglide/messages/commands_label.js +0 -84
  257. package/dist/ui/paraglide/messages/corporate.d.ts +0 -16
  258. package/dist/ui/paraglide/messages/corporate.js +0 -84
  259. package/dist/ui/paraglide/messages/cupcake.d.ts +0 -16
  260. package/dist/ui/paraglide/messages/cupcake.js +0 -84
  261. package/dist/ui/paraglide/messages/currency.d.ts +0 -16
  262. package/dist/ui/paraglide/messages/currency.js +0 -84
  263. package/dist/ui/paraglide/messages/current_currency.d.ts +0 -16
  264. package/dist/ui/paraglide/messages/current_currency.js +0 -84
  265. package/dist/ui/paraglide/messages/current_language.d.ts +0 -16
  266. package/dist/ui/paraglide/messages/current_language.js +0 -84
  267. package/dist/ui/paraglide/messages/current_theme.d.ts +0 -16
  268. package/dist/ui/paraglide/messages/current_theme.js +0 -84
  269. package/dist/ui/paraglide/messages/currently_in_queue.d.ts +0 -16
  270. package/dist/ui/paraglide/messages/currently_in_queue.js +0 -84
  271. package/dist/ui/paraglide/messages/cyberpunk.d.ts +0 -16
  272. package/dist/ui/paraglide/messages/cyberpunk.js +0 -84
  273. package/dist/ui/paraglide/messages/dark.d.ts +0 -16
  274. package/dist/ui/paraglide/messages/dark.js +0 -84
  275. package/dist/ui/paraglide/messages/de.d.ts +0 -16
  276. package/dist/ui/paraglide/messages/de.js +0 -84
  277. package/dist/ui/paraglide/messages/dim.d.ts +0 -16
  278. package/dist/ui/paraglide/messages/dim.js +0 -84
  279. package/dist/ui/paraglide/messages/dracula.d.ts +0 -16
  280. package/dist/ui/paraglide/messages/dracula.js +0 -84
  281. package/dist/ui/paraglide/messages/emerald.d.ts +0 -16
  282. package/dist/ui/paraglide/messages/emerald.js +0 -84
  283. package/dist/ui/paraglide/messages/en.d.ts +0 -16
  284. package/dist/ui/paraglide/messages/en.js +0 -84
  285. package/dist/ui/paraglide/messages/es.d.ts +0 -16
  286. package/dist/ui/paraglide/messages/es.js +0 -84
  287. package/dist/ui/paraglide/messages/eur3.d.ts +0 -17
  288. package/dist/ui/paraglide/messages/eur3.js +0 -85
  289. package/dist/ui/paraglide/messages/extended_controls.d.ts +0 -16
  290. package/dist/ui/paraglide/messages/extended_controls.js +0 -84
  291. package/dist/ui/paraglide/messages/fantasy.d.ts +0 -16
  292. package/dist/ui/paraglide/messages/fantasy.js +0 -84
  293. package/dist/ui/paraglide/messages/forest.d.ts +0 -16
  294. package/dist/ui/paraglide/messages/forest.js +0 -84
  295. package/dist/ui/paraglide/messages/fr.d.ts +0 -16
  296. package/dist/ui/paraglide/messages/fr.js +0 -84
  297. package/dist/ui/paraglide/messages/garden.d.ts +0 -16
  298. package/dist/ui/paraglide/messages/garden.js +0 -84
  299. package/dist/ui/paraglide/messages/gbp3.d.ts +0 -17
  300. package/dist/ui/paraglide/messages/gbp3.js +0 -85
  301. package/dist/ui/paraglide/messages/halloween.d.ts +0 -16
  302. package/dist/ui/paraglide/messages/halloween.js +0 -84
  303. package/dist/ui/paraglide/messages/hello_world.d.ts +0 -18
  304. package/dist/ui/paraglide/messages/hello_world.js +0 -84
  305. package/dist/ui/paraglide/messages/hi.d.ts +0 -16
  306. package/dist/ui/paraglide/messages/hi.js +0 -84
  307. package/dist/ui/paraglide/messages/inr3.d.ts +0 -17
  308. package/dist/ui/paraglide/messages/inr3.js +0 -85
  309. package/dist/ui/paraglide/messages/it.d.ts +0 -16
  310. package/dist/ui/paraglide/messages/it.js +0 -84
  311. package/dist/ui/paraglide/messages/ja.d.ts +0 -16
  312. package/dist/ui/paraglide/messages/ja.js +0 -84
  313. package/dist/ui/paraglide/messages/jpy3.d.ts +0 -17
  314. package/dist/ui/paraglide/messages/jpy3.js +0 -85
  315. package/dist/ui/paraglide/messages/ko.d.ts +0 -16
  316. package/dist/ui/paraglide/messages/ko.js +0 -84
  317. package/dist/ui/paraglide/messages/krw3.d.ts +0 -17
  318. package/dist/ui/paraglide/messages/krw3.js +0 -85
  319. package/dist/ui/paraglide/messages/languages.d.ts +0 -16
  320. package/dist/ui/paraglide/messages/languages.js +0 -84
  321. package/dist/ui/paraglide/messages/lemonade.d.ts +0 -16
  322. package/dist/ui/paraglide/messages/lemonade.js +0 -84
  323. package/dist/ui/paraglide/messages/light.d.ts +0 -16
  324. package/dist/ui/paraglide/messages/light.js +0 -84
  325. package/dist/ui/paraglide/messages/live_store_dashboard.d.ts +0 -16
  326. package/dist/ui/paraglide/messages/live_store_dashboard.js +0 -84
  327. package/dist/ui/paraglide/messages/localization.d.ts +0 -16
  328. package/dist/ui/paraglide/messages/localization.js +0 -84
  329. package/dist/ui/paraglide/messages/lofi.d.ts +0 -16
  330. package/dist/ui/paraglide/messages/lofi.js +0 -84
  331. package/dist/ui/paraglide/messages/luxury.d.ts +0 -16
  332. package/dist/ui/paraglide/messages/luxury.js +0 -84
  333. package/dist/ui/paraglide/messages/mxn3.d.ts +0 -17
  334. package/dist/ui/paraglide/messages/mxn3.js +0 -85
  335. package/dist/ui/paraglide/messages/name_label.d.ts +0 -16
  336. package/dist/ui/paraglide/messages/name_label.js +0 -84
  337. package/dist/ui/paraglide/messages/night.d.ts +0 -16
  338. package/dist/ui/paraglide/messages/night.js +0 -84
  339. package/dist/ui/paraglide/messages/nord.d.ts +0 -16
  340. package/dist/ui/paraglide/messages/nord.js +0 -84
  341. package/dist/ui/paraglide/messages/pastel.d.ts +0 -16
  342. package/dist/ui/paraglide/messages/pastel.js +0 -84
  343. package/dist/ui/paraglide/messages/pt.d.ts +0 -16
  344. package/dist/ui/paraglide/messages/pt.js +0 -84
  345. package/dist/ui/paraglide/messages/real_time_monitor_desc.d.ts +0 -16
  346. package/dist/ui/paraglide/messages/real_time_monitor_desc.js +0 -84
  347. package/dist/ui/paraglide/messages/registered_in_registry.d.ts +0 -16
  348. package/dist/ui/paraglide/messages/registered_in_registry.js +0 -84
  349. package/dist/ui/paraglide/messages/retro.d.ts +0 -16
  350. package/dist/ui/paraglide/messages/retro.js +0 -84
  351. package/dist/ui/paraglide/messages/ru.d.ts +0 -16
  352. package/dist/ui/paraglide/messages/ru.js +0 -84
  353. package/dist/ui/paraglide/messages/silk.d.ts +0 -16
  354. package/dist/ui/paraglide/messages/silk.js +0 -84
  355. package/dist/ui/paraglide/messages/state_label.d.ts +0 -16
  356. package/dist/ui/paraglide/messages/state_label.js +0 -84
  357. package/dist/ui/paraglide/messages/sunset.d.ts +0 -16
  358. package/dist/ui/paraglide/messages/sunset.js +0 -84
  359. package/dist/ui/paraglide/messages/synthwave.d.ts +0 -16
  360. package/dist/ui/paraglide/messages/synthwave.js +0 -84
  361. package/dist/ui/paraglide/messages/system.d.ts +0 -16
  362. package/dist/ui/paraglide/messages/system.js +0 -84
  363. package/dist/ui/paraglide/messages/theme.d.ts +0 -16
  364. package/dist/ui/paraglide/messages/theme.js +0 -84
  365. package/dist/ui/paraglide/messages/themes.d.ts +0 -16
  366. package/dist/ui/paraglide/messages/themes.js +0 -84
  367. package/dist/ui/paraglide/messages/url_label.d.ts +0 -16
  368. package/dist/ui/paraglide/messages/url_label.js +0 -84
  369. package/dist/ui/paraglide/messages/usd3.d.ts +0 -17
  370. package/dist/ui/paraglide/messages/usd3.js +0 -85
  371. package/dist/ui/paraglide/messages/valentine.d.ts +0 -16
  372. package/dist/ui/paraglide/messages/valentine.js +0 -84
  373. package/dist/ui/paraglide/messages/version_label.d.ts +0 -16
  374. package/dist/ui/paraglide/messages/version_label.js +0 -84
  375. package/dist/ui/paraglide/messages/vi.d.ts +0 -16
  376. package/dist/ui/paraglide/messages/vi.js +0 -84
  377. package/dist/ui/paraglide/messages/winter.d.ts +0 -16
  378. package/dist/ui/paraglide/messages/winter.js +0 -84
  379. package/dist/ui/paraglide/messages/wireframe.d.ts +0 -16
  380. package/dist/ui/paraglide/messages/wireframe.js +0 -84
  381. package/dist/ui/paraglide/messages/zh.d.ts +0 -16
  382. package/dist/ui/paraglide/messages/zh.js +0 -84
  383. package/dist/ui/paraglide/messages.d.ts +0 -2
  384. package/dist/ui/paraglide/messages.js +0 -4
  385. package/dist/ui/paraglide/registry.d.ts +0 -21
  386. package/dist/ui/paraglide/registry.js +0 -31
  387. package/dist/ui/paraglide/runtime.d.ts +0 -749
  388. package/dist/ui/paraglide/runtime.js +0 -1777
  389. package/dist/ui/paraglide/server.d.ts +0 -103
  390. package/dist/ui/paraglide/server.js +0 -248
  391. package/dist/ui/primitives/DatePicker.svelte +0 -257
  392. package/dist/ui/primitives/DatePicker.svelte.d.ts +0 -17
  393. package/dist/ui/primitives/index.d.ts +0 -1
  394. package/dist/ui/primitives/index.js +0 -3
  395. /package/{dist/ui/layout → src/runes/layout/src}/ContentArea.svelte +0 -0
  396. /package/{dist/ui/layout → src/runes/layout/src}/DetailPanel.svelte +0 -0
  397. /package/{dist/ui/features → src/runes/palettes/src}/notifications/NotificationBell.svelte +0 -0
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it, vi } from "vite-plus/test";
2
+ import { ExchangeRateStore } from "./exchange-rate.svelte.ts";
3
+ import { currencyStore, setExchangeRateStore } from "./currency.svelte.ts";
4
+ import { inMemoryDriver, RUNE_LAB_CONTEXT } from "@rune-lab/kernel";
5
+ import { languageStore } from "../../../layout/src/language.svelte.ts";
6
+
7
+ // Mock useMoney since it uses getContext and other stores
8
+ vi.mock("./useMoney.ts", () => ({
9
+ useMoney: () => ({
10
+ format: (amount: number, code?: string, _unit?: unknown) =>
11
+ `${amount} ${code ?? "USD"}`,
12
+ }),
13
+ }));
14
+
15
+ describe("ExchangeRateStore", () => {
16
+ it("should initialize with default base", () => {
17
+ const store = new ExchangeRateStore();
18
+ expect(store.baseCurrency).toBe("USD");
19
+ expect(store.hasRates).toBe(false);
20
+ });
21
+
22
+ it("should set and retrieve rates", () => {
23
+ const store = new ExchangeRateStore();
24
+ store.setRates("USD", { MXN: 17.23, EUR: 0.91 });
25
+
26
+ expect(store.hasRates).toBe(true);
27
+ expect(store.baseCurrency).toBe("USD");
28
+
29
+ const rateToMxn = store.getRate("USD", "MXN");
30
+ expect(rateToMxn).toEqual({ amount: 17230000, scale: 6 });
31
+ });
32
+
33
+ it("should handle inverse rates", () => {
34
+ const store = new ExchangeRateStore();
35
+ store.setRates("USD", { MXN: 20 });
36
+
37
+ const rateToBase = store.getRate("MXN", "USD");
38
+ expect(rateToBase).toEqual({ amount: 5000000, scale: 8 });
39
+ });
40
+
41
+ it("should handle triangulation (cross-rates)", () => {
42
+ const store = new ExchangeRateStore();
43
+ store.setRates("USD", { MXN: 20, EUR: 0.5 });
44
+
45
+ const rateMxnToEur = store.getRate("MXN", "EUR");
46
+ expect(rateMxnToEur).toEqual({ amount: 2500000, scale: 8 });
47
+ });
48
+ });
49
+
50
+ describe("CurrencyStore Integration", () => {
51
+ it("should convert amount when ExchangeRateStore is wired", () => {
52
+ const exchangeRateStore = new ExchangeRateStore();
53
+ exchangeRateStore.setRates("USD", { MXN: 20 });
54
+
55
+ setExchangeRateStore(exchangeRateStore);
56
+
57
+ expect(currencyStore.canConvert).toBe(true);
58
+
59
+ const converted = currencyStore.convertAmount(10000, "USD", "MXN");
60
+ expect(converted).toBe(200000);
61
+ });
62
+
63
+ it("should return original amount if no rates available", () => {
64
+ const exchangeRateStore = new ExchangeRateStore();
65
+ setExchangeRateStore(exchangeRateStore);
66
+
67
+ expect(currencyStore.canConvert).toBe(false);
68
+
69
+ const converted = currencyStore.convertAmount(10000, "USD", "MXN");
70
+ expect(converted).toBe(10000);
71
+ });
72
+ });
73
+
74
+ describe("useMoneyFilter", () => {
75
+ // Simple test for logic, skipping full component mount for brevity
76
+ it("matches() should work with conversion", () => {
77
+ const exchangeRateStore = new ExchangeRateStore();
78
+ exchangeRateStore.setRates("USD", { MXN: 20 });
79
+
80
+ setExchangeRateStore(exchangeRateStore);
81
+
82
+ // Manual mock of context for the composable
83
+ // In a real test we'd use render() from @testing-library/svelte
84
+ const mockContext = new Map();
85
+ mockContext.set(RUNE_LAB_CONTEXT.currency, currencyStore);
86
+ mockContext.set(RUNE_LAB_CONTEXT.exchangeRate, exchangeRateStore);
87
+ mockContext.set(RUNE_LAB_CONTEXT.language, languageStore);
88
+
89
+ // We can't easily call useMoneyFilter outside component without more setup
90
+ // but we can verify the matches logic in CurrencyStore
91
+ const amountInMxn = 1000000; // 10,000 MXN
92
+ const amountInUsd = currencyStore.convertAmount(amountInMxn, "MXN", "USD");
93
+ expect(amountInUsd).toBe(50000); // 500 USD
94
+ });
95
+ });
@@ -0,0 +1,79 @@
1
+ import { defineRune, RUNE_LAB_CONTEXT } from "@rune-lab/kernel";
2
+ import type { PersistenceDriver, RunePlugin } from "@rune-lab/kernel";
3
+ import {
4
+ type Currency,
5
+ currencyStore,
6
+ setExchangeRateStore,
7
+ } from "./currency.svelte.ts";
8
+ import { createExchangeRateStore } from "./exchange-rate.svelte.ts";
9
+ import type { ExchangeRateStore } from "./exchange-rate.svelte.ts";
10
+
11
+ export * from "./currency.svelte.ts";
12
+ export * from "./exchange-rate.svelte.ts";
13
+ export * from "./types.ts";
14
+ export * from "./money.ts";
15
+ export * from "./strategies.ts";
16
+ export * from "./money-primitive.ts";
17
+ export * from "./useMoney.ts";
18
+ export * from "./useMoneyFilter.ts";
19
+
20
+ interface MoneyConfig {
21
+ exchangeRates?: {
22
+ base: string;
23
+ rates: Record<string, number>;
24
+ };
25
+ currencies?: Currency[];
26
+ defaultCurrency?: string;
27
+ }
28
+
29
+ /**
30
+ * Money Plugin — provides currency management and exchange rate conversion.
31
+ */
32
+ export const MoneyPlugin: RunePlugin = defineRune({
33
+ id: "rune-lab.money",
34
+ stores: [
35
+ {
36
+ id: "exchangeRate",
37
+ contextKey: RUNE_LAB_CONTEXT.exchangeRate,
38
+ factory: (config: unknown) => {
39
+ const store = createExchangeRateStore();
40
+ const c = config as MoneyConfig;
41
+ if (c?.exchangeRates) {
42
+ store.setRates(c.exchangeRates.base, c.exchangeRates.rates);
43
+ }
44
+ return store;
45
+ },
46
+ noPersistence: true,
47
+ },
48
+ {
49
+ id: "currency",
50
+ contextKey: RUNE_LAB_CONTEXT.currency,
51
+ factory: (
52
+ config: unknown,
53
+ _driver: PersistenceDriver,
54
+ stores: Map<string, unknown>,
55
+ ) => {
56
+ const c = config as MoneyConfig;
57
+ setExchangeRateStore(stores.get("exchangeRate") as ExchangeRateStore);
58
+
59
+ if (c?.currencies) {
60
+ for (const cur of c.currencies) {
61
+ currencyStore.addCurrency(cur);
62
+ }
63
+ }
64
+
65
+ if (
66
+ c?.defaultCurrency &&
67
+ !(currencyStore as unknown as { current: unknown }).current
68
+ ) {
69
+ if (currencyStore.get(c.defaultCurrency as never)) {
70
+ currencyStore.set(c.defaultCurrency as never);
71
+ }
72
+ }
73
+
74
+ return currencyStore;
75
+ },
76
+ dependsOn: ["exchangeRate"],
77
+ },
78
+ ],
79
+ });
@@ -0,0 +1,217 @@
1
+ import { describe, expect, it } from "vite-plus/test";
2
+ import { MoneyPrimitive } from "./money-primitive.ts";
3
+ import { registerCurrency } from "./money.ts";
4
+
5
+ describe("MoneyPrimitive", () => {
6
+ describe("Construction", () => {
7
+ it("should create from minor units", () => {
8
+ const money = MoneyPrimitive.fromMinor(1299, "USD");
9
+ expect(money.amount).toBe(1299);
10
+ expect(money.currencyCode).toBe("USD");
11
+ expect(money.scale).toBe(2);
12
+ });
13
+
14
+ it("should create from major units", () => {
15
+ const money = MoneyPrimitive.fromMajor(12.99, "USD");
16
+ expect(money.amount).toBe(1299);
17
+ expect(money.currencyCode).toBe("USD");
18
+ expect(money.scale).toBe(2);
19
+ });
20
+
21
+ it("should round fractional minor units", () => {
22
+ const money = MoneyPrimitive.fromMajor(12.999, "USD");
23
+ expect(money.amount).toBe(1300); // rounded
24
+ });
25
+
26
+ it("should throw for unknown currency", () => {
27
+ expect(() => MoneyPrimitive.fromMinor(100, "UNKNOWN")).toThrow(
28
+ "Unknown currency code",
29
+ );
30
+ });
31
+ });
32
+
33
+ describe("JPY (exponent 0)", () => {
34
+ it("should handle zero-exponent currencies", () => {
35
+ const yen = MoneyPrimitive.fromMinor(5000, "JPY");
36
+ expect(yen.minor).toBe(5000);
37
+ expect(yen.major).toBe(5000); // no decimal shift for JPY
38
+ expect(yen.scale).toBe(0);
39
+ });
40
+
41
+ it("should create from major with no decimal shift", () => {
42
+ const yen = MoneyPrimitive.fromMajor(5000, "JPY");
43
+ expect(yen.amount).toBe(5000);
44
+ });
45
+ });
46
+
47
+ describe("BHD (exponent 3)", () => {
48
+ it("should handle 3-exponent currencies", () => {
49
+ const BHD = { code: "BHD", base: 10, exponent: 3 };
50
+ registerCurrency("BHD", BHD);
51
+
52
+ const dinar = MoneyPrimitive.fromMajor(1.234, "BHD");
53
+ expect(dinar.amount).toBe(1234);
54
+ expect(dinar.major).toBe(1.234);
55
+ expect(dinar.scale).toBe(3);
56
+ });
57
+ });
58
+
59
+ describe("Getters", () => {
60
+ it(".minor returns the raw amount", () => {
61
+ const money = MoneyPrimitive.fromMinor(1500, "USD");
62
+ expect(money.minor).toBe(1500);
63
+ });
64
+
65
+ it(".major returns the human-readable float", () => {
66
+ const money = MoneyPrimitive.fromMinor(1500, "USD");
67
+ expect(money.major).toBe(15.0);
68
+ });
69
+
70
+ it(".major handles MXN (exponent 2)", () => {
71
+ const money = MoneyPrimitive.fromMinor(25050, "MXN");
72
+ expect(money.major).toBe(250.5);
73
+ });
74
+ });
75
+
76
+ describe("Formatting", () => {
77
+ it("should format USD in en-US", () => {
78
+ const money = MoneyPrimitive.fromMinor(1299, "USD");
79
+ expect(money.format("en-US")).toMatch(/\$12\.99/);
80
+ });
81
+
82
+ it("should format MXN in es-MX", () => {
83
+ const money = MoneyPrimitive.fromMinor(25050, "MXN");
84
+ expect(money.format("es-MX")).toMatch(/\$250\.50/);
85
+ });
86
+
87
+ it("should format JPY with no decimals", () => {
88
+ const money = MoneyPrimitive.fromMinor(5000, "JPY");
89
+ const formatted = money.format("ja-JP");
90
+ expect(formatted).toMatch(/5,000/);
91
+ });
92
+ });
93
+
94
+ describe("Arithmetic (immutability)", () => {
95
+ it("add returns a new instance", () => {
96
+ const a = MoneyPrimitive.fromMinor(1000, "USD");
97
+ const b = MoneyPrimitive.fromMinor(500, "USD");
98
+ const result = a.add(b);
99
+
100
+ expect(result.amount).toBe(1500);
101
+ expect(result).not.toBe(a); // new instance
102
+ expect(a.amount).toBe(1000); // original unchanged
103
+ });
104
+
105
+ it("subtract returns a new instance", () => {
106
+ const a = MoneyPrimitive.fromMinor(1000, "USD");
107
+ const b = MoneyPrimitive.fromMinor(300, "USD");
108
+ const result = a.subtract(b);
109
+
110
+ expect(result.amount).toBe(700);
111
+ expect(a.amount).toBe(1000);
112
+ });
113
+
114
+ it("multiply returns a new instance", () => {
115
+ const money = MoneyPrimitive.fromMinor(1000, "USD");
116
+ const result = money.multiply(2.5);
117
+
118
+ expect(result.amount).toBe(2500);
119
+ expect(money.amount).toBe(1000);
120
+ });
121
+
122
+ it("multiply rounds correctly", () => {
123
+ const money = MoneyPrimitive.fromMinor(100, "USD");
124
+ const result = money.multiply(0.33);
125
+
126
+ expect(result.amount).toBe(33);
127
+ });
128
+
129
+ it("throws on currency mismatch for add", () => {
130
+ const usd = MoneyPrimitive.fromMinor(100, "USD");
131
+ const eur = MoneyPrimitive.fromMinor(100, "EUR");
132
+
133
+ expect(() => usd.add(eur)).toThrow("Currency mismatch");
134
+ });
135
+
136
+ it("throws on currency mismatch for subtract", () => {
137
+ const usd = MoneyPrimitive.fromMinor(100, "USD");
138
+ const eur = MoneyPrimitive.fromMinor(100, "EUR");
139
+
140
+ expect(() => usd.subtract(eur)).toThrow("Currency mismatch");
141
+ });
142
+ });
143
+
144
+ describe("Comparison", () => {
145
+ it("equals returns true for identical values", () => {
146
+ const a = MoneyPrimitive.fromMinor(1000, "USD");
147
+ const b = MoneyPrimitive.fromMinor(1000, "USD");
148
+ expect(a.equals(b)).toBe(true);
149
+ });
150
+
151
+ it("equals returns false for different amounts", () => {
152
+ const a = MoneyPrimitive.fromMinor(1000, "USD");
153
+ const b = MoneyPrimitive.fromMinor(2000, "USD");
154
+ expect(a.equals(b)).toBe(false);
155
+ });
156
+
157
+ it("equals returns false for different currencies", () => {
158
+ const a = MoneyPrimitive.fromMinor(1000, "USD");
159
+ const b = MoneyPrimitive.fromMinor(1000, "EUR");
160
+ expect(a.equals(b)).toBe(false);
161
+ });
162
+
163
+ it("isZero returns true for zero amount", () => {
164
+ expect(MoneyPrimitive.fromMinor(0, "USD").isZero()).toBe(true);
165
+ });
166
+
167
+ it("isZero returns false for non-zero", () => {
168
+ expect(MoneyPrimitive.fromMinor(1, "USD").isZero()).toBe(false);
169
+ });
170
+
171
+ it("isNegative works correctly", () => {
172
+ expect(MoneyPrimitive.fromMinor(-100, "USD").isNegative()).toBe(true);
173
+ expect(MoneyPrimitive.fromMinor(0, "USD").isNegative()).toBe(false);
174
+ expect(MoneyPrimitive.fromMinor(100, "USD").isNegative()).toBe(false);
175
+ });
176
+ });
177
+
178
+ describe("Serialization (JSON round-trip)", () => {
179
+ it("should round-trip standard currency", () => {
180
+ const original = MoneyPrimitive.fromMinor(1299, "USD");
181
+ const json = original.toJSON();
182
+
183
+ expect(json).toEqual({
184
+ amount: 1299,
185
+ currencyCode: "USD",
186
+ scale: 2,
187
+ });
188
+
189
+ const restored = MoneyPrimitive.fromJSON(json);
190
+ expect(restored.equals(original)).toBe(true);
191
+ });
192
+
193
+ it("should round-trip JPY", () => {
194
+ const original = MoneyPrimitive.fromMinor(5000, "JPY");
195
+ const json = original.toJSON();
196
+ const restored = MoneyPrimitive.fromJSON(json);
197
+
198
+ expect(restored.amount).toBe(5000);
199
+ expect(restored.scale).toBe(0);
200
+ expect(restored.equals(original)).toBe(true);
201
+ });
202
+
203
+ it("should round-trip through JSON.stringify/parse", () => {
204
+ const original = MoneyPrimitive.fromMajor(42.5, "EUR");
205
+ const serialized = JSON.stringify(original);
206
+ const parsed = JSON.parse(serialized);
207
+ const restored = MoneyPrimitive.fromJSON(parsed);
208
+
209
+ expect(restored.equals(original)).toBe(true);
210
+ });
211
+
212
+ it("toString returns a debug-friendly string", () => {
213
+ const money = MoneyPrimitive.fromMinor(1299, "USD");
214
+ expect(money.toString()).toBe("MoneyPrimitive(1299 USD scale=2)");
215
+ });
216
+ });
217
+ });
@@ -0,0 +1,220 @@
1
+ // sdk/core/src/money/money-primitive.ts
2
+ // Immutable Value Object for monetary amounts.
3
+ // Zero Svelte dependencies — pure TypeScript class.
4
+
5
+ import { CURRENCY_MAP, type ISO4217Code } from "./money.ts";
6
+
7
+ /**
8
+ * Serializable representation of a MoneyPrimitive.
9
+ * Used for persistence drivers and JSON round-trips.
10
+ */
11
+ export interface MoneyJSON {
12
+ amount: number;
13
+ currencyCode: string;
14
+ scale: number;
15
+ }
16
+
17
+ /**
18
+ * Immutable Value Object that encapsulates a monetary amount, its currency,
19
+ * and the scale (number of decimal places).
20
+ *
21
+ * Amounts are always stored in minor units (e.g., cents for USD, centavos for MXN).
22
+ * The scale is derived from the currency's exponent in CURRENCY_MAP.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const price = MoneyPrimitive.fromMajor(12.99, "USD");
27
+ * price.minor; // 1299
28
+ * price.major; // 12.99
29
+ * price.format("en-US"); // "$12.99"
30
+ *
31
+ * const json = price.toJSON();
32
+ * const restored = MoneyPrimitive.fromJSON(json);
33
+ * ```
34
+ */
35
+ export class MoneyPrimitive {
36
+ /** Raw amount in minor units (e.g., cents). */
37
+ readonly amount: number;
38
+ /** ISO 4217 currency code. */
39
+ readonly currencyCode: string;
40
+ /** Number of decimal places for this currency (e.g., 2 for USD, 0 for JPY). */
41
+ readonly scale: number;
42
+
43
+ private constructor(amount: number, currencyCode: string, scale: number) {
44
+ this.amount = Math.round(amount);
45
+ this.currencyCode = currencyCode;
46
+ this.scale = scale;
47
+ }
48
+
49
+ // ── Factories ────────────────────────────────────────────────────────────
50
+
51
+ /**
52
+ * Create a MoneyPrimitive from a minor-unit integer amount.
53
+ * @param amount - Amount in minor units (e.g., 1299 for $12.99)
54
+ * @param currencyCode - ISO 4217 currency code
55
+ */
56
+ static fromMinor(
57
+ amount: number,
58
+ currencyCode: ISO4217Code | string,
59
+ ): MoneyPrimitive {
60
+ const scale = MoneyPrimitive.resolveScale(currencyCode);
61
+ return new MoneyPrimitive(amount, currencyCode, scale);
62
+ }
63
+
64
+ /**
65
+ * Create a MoneyPrimitive from a major-unit float amount.
66
+ * @param amount - Amount in major units (e.g., 12.99 for $12.99)
67
+ * @param currencyCode - ISO 4217 currency code
68
+ */
69
+ static fromMajor(
70
+ amount: number,
71
+ currencyCode: ISO4217Code | string,
72
+ ): MoneyPrimitive {
73
+ const scale = MoneyPrimitive.resolveScale(currencyCode);
74
+ const factor = Math.pow(10, scale);
75
+ return new MoneyPrimitive(Math.round(amount * factor), currencyCode, scale);
76
+ }
77
+
78
+ /**
79
+ * Restore a MoneyPrimitive from its JSON representation.
80
+ */
81
+ static fromJSON(json: MoneyJSON): MoneyPrimitive {
82
+ return new MoneyPrimitive(json.amount, json.currencyCode, json.scale);
83
+ }
84
+
85
+ // ── Computed Getters ─────────────────────────────────────────────────────
86
+
87
+ /** Amount in minor units (alias for `amount`). */
88
+ get minor(): number {
89
+ return this.amount;
90
+ }
91
+
92
+ /** Amount in major units (e.g., 12.99 for 1299 cents). */
93
+ get major(): number {
94
+ if (this.scale === 0) return this.amount;
95
+ return this.amount / Math.pow(10, this.scale);
96
+ }
97
+
98
+ // ── Formatting ───────────────────────────────────────────────────────────
99
+
100
+ /**
101
+ * Format this monetary value as a locale-aware currency string.
102
+ * @param locale - BCP 47 locale string (default: "en-US")
103
+ */
104
+ format(locale: string = "en-US"): string {
105
+ return new Intl.NumberFormat(locale, {
106
+ style: "currency",
107
+ currency: this.currencyCode,
108
+ minimumFractionDigits: this.scale,
109
+ maximumFractionDigits: this.scale,
110
+ }).format(this.major);
111
+ }
112
+
113
+ // ── Arithmetic (returns new instances — immutability) ────────────────────
114
+
115
+ /**
116
+ * Add another MoneyPrimitive (must be same currency).
117
+ * Returns a new MoneyPrimitive.
118
+ */
119
+ add(other: MoneyPrimitive): MoneyPrimitive {
120
+ this.assertSameCurrency(other);
121
+ return new MoneyPrimitive(
122
+ this.amount + other.amount,
123
+ this.currencyCode,
124
+ this.scale,
125
+ );
126
+ }
127
+
128
+ /**
129
+ * Subtract another MoneyPrimitive (must be same currency).
130
+ * Returns a new MoneyPrimitive.
131
+ */
132
+ subtract(other: MoneyPrimitive): MoneyPrimitive {
133
+ this.assertSameCurrency(other);
134
+ return new MoneyPrimitive(
135
+ this.amount - other.amount,
136
+ this.currencyCode,
137
+ this.scale,
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Multiply by a scalar factor. Returns a new MoneyPrimitive.
143
+ */
144
+ multiply(factor: number): MoneyPrimitive {
145
+ return new MoneyPrimitive(
146
+ Math.round(this.amount * factor),
147
+ this.currencyCode,
148
+ this.scale,
149
+ );
150
+ }
151
+
152
+ // ── Comparison ───────────────────────────────────────────────────────────
153
+
154
+ /**
155
+ * Value equality — two MoneyPrimitives are equal if they have the same
156
+ * amount, currency code, and scale.
157
+ */
158
+ equals(other: MoneyPrimitive): boolean {
159
+ return (
160
+ this.amount === other.amount &&
161
+ this.currencyCode === other.currencyCode &&
162
+ this.scale === other.scale
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Returns true if this amount is zero.
168
+ */
169
+ isZero(): boolean {
170
+ return this.amount === 0;
171
+ }
172
+
173
+ /**
174
+ * Returns true if this amount is negative.
175
+ */
176
+ isNegative(): boolean {
177
+ return this.amount < 0;
178
+ }
179
+
180
+ // ── Serialization ────────────────────────────────────────────────────────
181
+
182
+ /**
183
+ * Serialize to a plain JSON-compatible object.
184
+ */
185
+ toJSON(): MoneyJSON {
186
+ return {
187
+ amount: this.amount,
188
+ currencyCode: this.currencyCode,
189
+ scale: this.scale,
190
+ };
191
+ }
192
+
193
+ toString(): string {
194
+ return `MoneyPrimitive(${this.amount} ${this.currencyCode} scale=${this.scale})`;
195
+ }
196
+
197
+ // ── Private Helpers ──────────────────────────────────────────────────────
198
+
199
+ private assertSameCurrency(other: MoneyPrimitive): void {
200
+ if (this.currencyCode !== other.currencyCode) {
201
+ throw new Error(
202
+ `Currency mismatch: cannot operate on ${this.currencyCode} and ${other.currencyCode}`,
203
+ );
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Resolves the scale (exponent) for a currency code from CURRENCY_MAP.
209
+ * Throws if the currency is not registered.
210
+ */
211
+ private static resolveScale(currencyCode: string): number {
212
+ const currency = CURRENCY_MAP[currencyCode];
213
+ if (!currency) {
214
+ throw new Error(
215
+ `Unknown currency code: ${currencyCode}. Register it first via registerCurrency().`,
216
+ );
217
+ }
218
+ return currency.exponent;
219
+ }
220
+ }