ugly-app 0.1.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 (554) hide show
  1. package/.claude/settings.local.json +18 -0
  2. package/.env.example +9 -0
  3. package/.yarn-metadata.json +113 -0
  4. package/README.md +709 -0
  5. package/dist/cli/authCommands.d.ts +6 -0
  6. package/dist/cli/authCommands.d.ts.map +1 -0
  7. package/dist/cli/authCommands.js +50 -0
  8. package/dist/cli/authCommands.js.map +1 -0
  9. package/dist/cli/build.d.ts +2 -0
  10. package/dist/cli/build.d.ts.map +1 -0
  11. package/dist/cli/build.js +26 -0
  12. package/dist/cli/build.js.map +1 -0
  13. package/dist/cli/configure.d.ts +2 -0
  14. package/dist/cli/configure.d.ts.map +1 -0
  15. package/dist/cli/configure.js +53 -0
  16. package/dist/cli/configure.js.map +1 -0
  17. package/dist/cli/dev.d.ts +2 -0
  18. package/dist/cli/dev.d.ts.map +1 -0
  19. package/dist/cli/dev.js +66 -0
  20. package/dist/cli/dev.js.map +1 -0
  21. package/dist/cli/index.d.ts +3 -0
  22. package/dist/cli/index.d.ts.map +1 -0
  23. package/dist/cli/index.js +340 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/cli/initDb.d.ts +2 -0
  26. package/dist/cli/initDb.d.ts.map +1 -0
  27. package/dist/cli/initDb.js +110 -0
  28. package/dist/cli/initDb.js.map +1 -0
  29. package/dist/cli/logQuery.d.ts +12 -0
  30. package/dist/cli/logQuery.d.ts.map +1 -0
  31. package/dist/cli/logQuery.js +176 -0
  32. package/dist/cli/logQuery.js.map +1 -0
  33. package/dist/cli/migrate.d.ts +4 -0
  34. package/dist/cli/migrate.d.ts.map +1 -0
  35. package/dist/cli/migrate.js +62 -0
  36. package/dist/cli/migrate.js.map +1 -0
  37. package/dist/cli/publishAssets.d.ts +6 -0
  38. package/dist/cli/publishAssets.d.ts.map +1 -0
  39. package/dist/cli/publishAssets.js +87 -0
  40. package/dist/cli/publishAssets.js.map +1 -0
  41. package/dist/cli/purgeAssets.d.ts +2 -0
  42. package/dist/cli/purgeAssets.d.ts.map +1 -0
  43. package/dist/cli/purgeAssets.js +36 -0
  44. package/dist/cli/purgeAssets.js.map +1 -0
  45. package/dist/cli/scaffold.d.ts +2 -0
  46. package/dist/cli/scaffold.d.ts.map +1 -0
  47. package/dist/cli/scaffold.js +37 -0
  48. package/dist/cli/scaffold.js.map +1 -0
  49. package/dist/cli/serverLogQuery.d.ts +6 -0
  50. package/dist/cli/serverLogQuery.d.ts.map +1 -0
  51. package/dist/cli/serverLogQuery.js +76 -0
  52. package/dist/cli/serverLogQuery.js.map +1 -0
  53. package/dist/cli/storageClient.d.ts +3 -0
  54. package/dist/cli/storageClient.d.ts.map +1 -0
  55. package/dist/cli/storageClient.js +22 -0
  56. package/dist/cli/storageClient.js.map +1 -0
  57. package/dist/cli/uglyappConfig.d.ts +29 -0
  58. package/dist/cli/uglyappConfig.d.ts.map +1 -0
  59. package/dist/cli/uglyappConfig.js +125 -0
  60. package/dist/cli/uglyappConfig.js.map +1 -0
  61. package/dist/client/AppProvider.d.ts +32 -0
  62. package/dist/client/AppProvider.d.ts.map +1 -0
  63. package/dist/client/AppProvider.js +126 -0
  64. package/dist/client/AppProvider.js.map +1 -0
  65. package/dist/client/ErrorLog.d.ts +16 -0
  66. package/dist/client/ErrorLog.d.ts.map +1 -0
  67. package/dist/client/ErrorLog.js +28 -0
  68. package/dist/client/ErrorLog.js.map +1 -0
  69. package/dist/client/FeedbackContext.d.ts +7 -0
  70. package/dist/client/FeedbackContext.d.ts.map +1 -0
  71. package/dist/client/FeedbackContext.js +15 -0
  72. package/dist/client/FeedbackContext.js.map +1 -0
  73. package/dist/client/Logger.d.ts +2 -0
  74. package/dist/client/Logger.d.ts.map +1 -0
  75. package/dist/client/Logger.js +72 -0
  76. package/dist/client/Logger.js.map +1 -0
  77. package/dist/client/LoginPopup.d.ts +7 -0
  78. package/dist/client/LoginPopup.d.ts.map +1 -0
  79. package/dist/client/LoginPopup.js +55 -0
  80. package/dist/client/LoginPopup.js.map +1 -0
  81. package/dist/client/Router.d.ts +56 -0
  82. package/dist/client/Router.d.ts.map +1 -0
  83. package/dist/client/Router.js +224 -0
  84. package/dist/client/Router.js.map +1 -0
  85. package/dist/client/Screenshot.d.ts +7 -0
  86. package/dist/client/Screenshot.d.ts.map +1 -0
  87. package/dist/client/Screenshot.js +54 -0
  88. package/dist/client/Screenshot.js.map +1 -0
  89. package/dist/client/ViewFlipper.d.ts +16 -0
  90. package/dist/client/ViewFlipper.d.ts.map +1 -0
  91. package/dist/client/ViewFlipper.js +156 -0
  92. package/dist/client/ViewFlipper.js.map +1 -0
  93. package/dist/client/animation/Animated.d.ts +14 -0
  94. package/dist/client/animation/Animated.d.ts.map +1 -0
  95. package/dist/client/animation/Animated.js +97 -0
  96. package/dist/client/animation/Animated.js.map +1 -0
  97. package/dist/client/animation/animatedValue.d.ts +41 -0
  98. package/dist/client/animation/animatedValue.d.ts.map +1 -0
  99. package/dist/client/animation/animatedValue.js +123 -0
  100. package/dist/client/animation/animatedValue.js.map +1 -0
  101. package/dist/client/animations/FadeIn.d.ts +6 -0
  102. package/dist/client/animations/FadeIn.d.ts.map +1 -0
  103. package/dist/client/animations/FadeIn.js +24 -0
  104. package/dist/client/animations/FadeIn.js.map +1 -0
  105. package/dist/client/animations/SlideFromBottom.d.ts +6 -0
  106. package/dist/client/animations/SlideFromBottom.d.ts.map +1 -0
  107. package/dist/client/animations/SlideFromBottom.js +24 -0
  108. package/dist/client/animations/SlideFromBottom.js.map +1 -0
  109. package/dist/client/animations/SlideFromRight.d.ts +6 -0
  110. package/dist/client/animations/SlideFromRight.d.ts.map +1 -0
  111. package/dist/client/animations/SlideFromRight.js +24 -0
  112. package/dist/client/animations/SlideFromRight.js.map +1 -0
  113. package/dist/client/audio/AudioPlayer.d.ts +11 -0
  114. package/dist/client/audio/AudioPlayer.d.ts.map +1 -0
  115. package/dist/client/audio/AudioPlayer.js +51 -0
  116. package/dist/client/audio/AudioPlayer.js.map +1 -0
  117. package/dist/client/audio/AudioRecorder.d.ts +11 -0
  118. package/dist/client/audio/AudioRecorder.d.ts.map +1 -0
  119. package/dist/client/audio/AudioRecorder.js +66 -0
  120. package/dist/client/audio/AudioRecorder.js.map +1 -0
  121. package/dist/client/audio/audioPlayer.worklet.d.ts +2 -0
  122. package/dist/client/audio/audioPlayer.worklet.d.ts.map +1 -0
  123. package/dist/client/audio/audioPlayer.worklet.js +46 -0
  124. package/dist/client/audio/audioPlayer.worklet.js.map +1 -0
  125. package/dist/client/audio/useSTT.d.ts +19 -0
  126. package/dist/client/audio/useSTT.d.ts.map +1 -0
  127. package/dist/client/audio/useSTT.js +91 -0
  128. package/dist/client/audio/useSTT.js.map +1 -0
  129. package/dist/client/audio/useTTS.d.ts +17 -0
  130. package/dist/client/audio/useTTS.d.ts.map +1 -0
  131. package/dist/client/audio/useTTS.js +70 -0
  132. package/dist/client/audio/useTTS.js.map +1 -0
  133. package/dist/client/components/Button.d.ts +22 -0
  134. package/dist/client/components/Button.d.ts.map +1 -0
  135. package/dist/client/components/Button.js +27 -0
  136. package/dist/client/components/Button.js.map +1 -0
  137. package/dist/client/components/Card.d.ts +8 -0
  138. package/dist/client/components/Card.d.ts.map +1 -0
  139. package/dist/client/components/Card.js +6 -0
  140. package/dist/client/components/Card.js.map +1 -0
  141. package/dist/client/components/EnumInput.d.ts +16 -0
  142. package/dist/client/components/EnumInput.d.ts.map +1 -0
  143. package/dist/client/components/EnumInput.js +11 -0
  144. package/dist/client/components/EnumInput.js.map +1 -0
  145. package/dist/client/components/FeedbackButton.d.ts +9 -0
  146. package/dist/client/components/FeedbackButton.d.ts.map +1 -0
  147. package/dist/client/components/FeedbackButton.js +112 -0
  148. package/dist/client/components/FeedbackButton.js.map +1 -0
  149. package/dist/client/components/Header.d.ts +10 -0
  150. package/dist/client/components/Header.d.ts.map +1 -0
  151. package/dist/client/components/Header.js +12 -0
  152. package/dist/client/components/Header.js.map +1 -0
  153. package/dist/client/components/Image.d.ts +20 -0
  154. package/dist/client/components/Image.d.ts.map +1 -0
  155. package/dist/client/components/Image.js +12 -0
  156. package/dist/client/components/Image.js.map +1 -0
  157. package/dist/client/components/Input.d.ts +14 -0
  158. package/dist/client/components/Input.d.ts.map +1 -0
  159. package/dist/client/components/Input.js +19 -0
  160. package/dist/client/components/Input.js.map +1 -0
  161. package/dist/client/components/Modal.d.ts +9 -0
  162. package/dist/client/components/Modal.d.ts.map +1 -0
  163. package/dist/client/components/Modal.js +19 -0
  164. package/dist/client/components/Modal.js.map +1 -0
  165. package/dist/client/components/PageLayout.d.ts +9 -0
  166. package/dist/client/components/PageLayout.d.ts.map +1 -0
  167. package/dist/client/components/PageLayout.js +6 -0
  168. package/dist/client/components/PageLayout.js.map +1 -0
  169. package/dist/client/components/Panel.d.ts +8 -0
  170. package/dist/client/components/Panel.d.ts.map +1 -0
  171. package/dist/client/components/Panel.js +5 -0
  172. package/dist/client/components/Panel.js.map +1 -0
  173. package/dist/client/components/PopupPanel.d.ts +9 -0
  174. package/dist/client/components/PopupPanel.d.ts.map +1 -0
  175. package/dist/client/components/PopupPanel.js +5 -0
  176. package/dist/client/components/PopupPanel.js.map +1 -0
  177. package/dist/client/components/Pressable.d.ts +14 -0
  178. package/dist/client/components/Pressable.d.ts.map +1 -0
  179. package/dist/client/components/Pressable.js +8 -0
  180. package/dist/client/components/Pressable.js.map +1 -0
  181. package/dist/client/components/ScrollView.d.ts +9 -0
  182. package/dist/client/components/ScrollView.d.ts.map +1 -0
  183. package/dist/client/components/ScrollView.js +7 -0
  184. package/dist/client/components/ScrollView.js.map +1 -0
  185. package/dist/client/components/SettingGroup.d.ts +10 -0
  186. package/dist/client/components/SettingGroup.d.ts.map +1 -0
  187. package/dist/client/components/SettingGroup.js +8 -0
  188. package/dist/client/components/SettingGroup.js.map +1 -0
  189. package/dist/client/components/Text.d.ts +21 -0
  190. package/dist/client/components/Text.d.ts.map +1 -0
  191. package/dist/client/components/Text.js +43 -0
  192. package/dist/client/components/Text.js.map +1 -0
  193. package/dist/client/components/Toast.d.ts +10 -0
  194. package/dist/client/components/Toast.d.ts.map +1 -0
  195. package/dist/client/components/Toast.js +21 -0
  196. package/dist/client/components/Toast.js.map +1 -0
  197. package/dist/client/components/View.d.ts +9 -0
  198. package/dist/client/components/View.d.ts.map +1 -0
  199. package/dist/client/components/View.js +5 -0
  200. package/dist/client/components/View.js.map +1 -0
  201. package/dist/client/components/index.d.ts +33 -0
  202. package/dist/client/components/index.d.ts.map +1 -0
  203. package/dist/client/components/index.js +17 -0
  204. package/dist/client/components/index.js.map +1 -0
  205. package/dist/client/components/zIndex.d.ts +10 -0
  206. package/dist/client/components/zIndex.d.ts.map +1 -0
  207. package/dist/client/components/zIndex.js +12 -0
  208. package/dist/client/components/zIndex.js.map +1 -0
  209. package/dist/client/createSocket.d.ts +26 -0
  210. package/dist/client/createSocket.d.ts.map +1 -0
  211. package/dist/client/createSocket.js +138 -0
  212. package/dist/client/createSocket.js.map +1 -0
  213. package/dist/client/index.d.ts +27 -0
  214. package/dist/client/index.d.ts.map +1 -0
  215. package/dist/client/index.js +20 -0
  216. package/dist/client/index.js.map +1 -0
  217. package/dist/playwright/index.d.ts +16 -0
  218. package/dist/playwright/index.d.ts.map +1 -0
  219. package/dist/playwright/index.js +32 -0
  220. package/dist/playwright/index.js.map +1 -0
  221. package/dist/server/App.d.ts +43 -0
  222. package/dist/server/App.d.ts.map +1 -0
  223. package/dist/server/App.js +215 -0
  224. package/dist/server/App.js.map +1 -0
  225. package/dist/server/Auth.d.ts +21 -0
  226. package/dist/server/Auth.d.ts.map +1 -0
  227. package/dist/server/Auth.js +171 -0
  228. package/dist/server/Auth.js.map +1 -0
  229. package/dist/server/Cache.d.ts +38 -0
  230. package/dist/server/Cache.d.ts.map +1 -0
  231. package/dist/server/Cache.js +113 -0
  232. package/dist/server/Cache.js.map +1 -0
  233. package/dist/server/ChangeStream.d.ts +22 -0
  234. package/dist/server/ChangeStream.d.ts.map +1 -0
  235. package/dist/server/ChangeStream.js +93 -0
  236. package/dist/server/ChangeStream.js.map +1 -0
  237. package/dist/server/DB.d.ts +17 -0
  238. package/dist/server/DB.d.ts.map +1 -0
  239. package/dist/server/DB.js +442 -0
  240. package/dist/server/DB.js.map +1 -0
  241. package/dist/server/Email.d.ts +22 -0
  242. package/dist/server/Email.d.ts.map +1 -0
  243. package/dist/server/Email.js +64 -0
  244. package/dist/server/Email.js.map +1 -0
  245. package/dist/server/EmailTemplate.d.ts +17 -0
  246. package/dist/server/EmailTemplate.d.ts.map +1 -0
  247. package/dist/server/EmailTemplate.js +29 -0
  248. package/dist/server/EmailTemplate.js.map +1 -0
  249. package/dist/server/FeedbackReport.d.ts +19 -0
  250. package/dist/server/FeedbackReport.d.ts.map +1 -0
  251. package/dist/server/FeedbackReport.js +94 -0
  252. package/dist/server/FeedbackReport.js.map +1 -0
  253. package/dist/server/ImageGen.d.ts +5 -0
  254. package/dist/server/ImageGen.d.ts.map +1 -0
  255. package/dist/server/ImageGen.js +5 -0
  256. package/dist/server/ImageGen.js.map +1 -0
  257. package/dist/server/Logging.d.ts +59 -0
  258. package/dist/server/Logging.d.ts.map +1 -0
  259. package/dist/server/Logging.js +293 -0
  260. package/dist/server/Logging.js.map +1 -0
  261. package/dist/server/Nats.d.ts +56 -0
  262. package/dist/server/Nats.d.ts.map +1 -0
  263. package/dist/server/Nats.js +162 -0
  264. package/dist/server/Nats.js.map +1 -0
  265. package/dist/server/NatsStore.d.ts +26 -0
  266. package/dist/server/NatsStore.d.ts.map +1 -0
  267. package/dist/server/NatsStore.js +86 -0
  268. package/dist/server/NatsStore.js.map +1 -0
  269. package/dist/server/PushNotification.d.ts +33 -0
  270. package/dist/server/PushNotification.d.ts.map +1 -0
  271. package/dist/server/PushNotification.js +131 -0
  272. package/dist/server/PushNotification.js.map +1 -0
  273. package/dist/server/RateLimit.d.ts +28 -0
  274. package/dist/server/RateLimit.d.ts.map +1 -0
  275. package/dist/server/RateLimit.js +101 -0
  276. package/dist/server/RateLimit.js.map +1 -0
  277. package/dist/server/Redis.d.ts +61 -0
  278. package/dist/server/Redis.d.ts.map +1 -0
  279. package/dist/server/Redis.js +226 -0
  280. package/dist/server/Redis.js.map +1 -0
  281. package/dist/server/Router.d.ts +27 -0
  282. package/dist/server/Router.d.ts.map +1 -0
  283. package/dist/server/Router.js +71 -0
  284. package/dist/server/Router.js.map +1 -0
  285. package/dist/server/Socket.d.ts +8 -0
  286. package/dist/server/Socket.d.ts.map +1 -0
  287. package/dist/server/Socket.js +278 -0
  288. package/dist/server/Socket.js.map +1 -0
  289. package/dist/server/SpendTracker.d.ts +18 -0
  290. package/dist/server/SpendTracker.d.ts.map +1 -0
  291. package/dist/server/SpendTracker.js +110 -0
  292. package/dist/server/SpendTracker.js.map +1 -0
  293. package/dist/server/Storage.d.ts +16 -0
  294. package/dist/server/Storage.d.ts.map +1 -0
  295. package/dist/server/Storage.js +95 -0
  296. package/dist/server/Storage.js.map +1 -0
  297. package/dist/server/TextGen.d.ts +5 -0
  298. package/dist/server/TextGen.d.ts.map +1 -0
  299. package/dist/server/TextGen.js +6 -0
  300. package/dist/server/TextGen.js.map +1 -0
  301. package/dist/server/WorkerQueue.d.ts +27 -0
  302. package/dist/server/WorkerQueue.d.ts.map +1 -0
  303. package/dist/server/WorkerQueue.js +147 -0
  304. package/dist/server/WorkerQueue.js.map +1 -0
  305. package/dist/server/ai/ImageGenClient.d.ts +6 -0
  306. package/dist/server/ai/ImageGenClient.d.ts.map +1 -0
  307. package/dist/server/ai/ImageGenClient.js +29 -0
  308. package/dist/server/ai/ImageGenClient.js.map +1 -0
  309. package/dist/server/ai/ProviderBalance.d.ts +15 -0
  310. package/dist/server/ai/ProviderBalance.d.ts.map +1 -0
  311. package/dist/server/ai/ProviderBalance.js +110 -0
  312. package/dist/server/ai/ProviderBalance.js.map +1 -0
  313. package/dist/server/ai/ProviderSelector.d.ts +13 -0
  314. package/dist/server/ai/ProviderSelector.d.ts.map +1 -0
  315. package/dist/server/ai/ProviderSelector.js +25 -0
  316. package/dist/server/ai/ProviderSelector.js.map +1 -0
  317. package/dist/server/ai/TextGenClient.d.ts +9 -0
  318. package/dist/server/ai/TextGenClient.d.ts.map +1 -0
  319. package/dist/server/ai/TextGenClient.js +72 -0
  320. package/dist/server/ai/TextGenClient.js.map +1 -0
  321. package/dist/server/ai/fallbacks.d.ts +9 -0
  322. package/dist/server/ai/fallbacks.d.ts.map +1 -0
  323. package/dist/server/ai/fallbacks.js +77 -0
  324. package/dist/server/ai/fallbacks.js.map +1 -0
  325. package/dist/server/ai/index.d.ts +12 -0
  326. package/dist/server/ai/index.d.ts.map +1 -0
  327. package/dist/server/ai/index.js +37 -0
  328. package/dist/server/ai/index.js.map +1 -0
  329. package/dist/server/ai/providers/Claude.d.ts +3 -0
  330. package/dist/server/ai/providers/Claude.d.ts.map +1 -0
  331. package/dist/server/ai/providers/Claude.js +124 -0
  332. package/dist/server/ai/providers/Claude.js.map +1 -0
  333. package/dist/server/ai/providers/FAL.d.ts +3 -0
  334. package/dist/server/ai/providers/FAL.d.ts.map +1 -0
  335. package/dist/server/ai/providers/FAL.js +24 -0
  336. package/dist/server/ai/providers/FAL.js.map +1 -0
  337. package/dist/server/ai/providers/Fireworks.d.ts +3 -0
  338. package/dist/server/ai/providers/Fireworks.d.ts.map +1 -0
  339. package/dist/server/ai/providers/Fireworks.js +79 -0
  340. package/dist/server/ai/providers/Fireworks.js.map +1 -0
  341. package/dist/server/ai/providers/Google.d.ts +3 -0
  342. package/dist/server/ai/providers/Google.d.ts.map +1 -0
  343. package/dist/server/ai/providers/Google.js +105 -0
  344. package/dist/server/ai/providers/Google.js.map +1 -0
  345. package/dist/server/ai/providers/GoogleImage.d.ts +3 -0
  346. package/dist/server/ai/providers/GoogleImage.d.ts.map +1 -0
  347. package/dist/server/ai/providers/GoogleImage.js +24 -0
  348. package/dist/server/ai/providers/GoogleImage.js.map +1 -0
  349. package/dist/server/ai/providers/Groq.d.ts +3 -0
  350. package/dist/server/ai/providers/Groq.d.ts.map +1 -0
  351. package/dist/server/ai/providers/Groq.js +99 -0
  352. package/dist/server/ai/providers/Groq.js.map +1 -0
  353. package/dist/server/ai/providers/Kie.d.ts +3 -0
  354. package/dist/server/ai/providers/Kie.d.ts.map +1 -0
  355. package/dist/server/ai/providers/Kie.js +79 -0
  356. package/dist/server/ai/providers/Kie.js.map +1 -0
  357. package/dist/server/ai/providers/KieImage.d.ts +3 -0
  358. package/dist/server/ai/providers/KieImage.d.ts.map +1 -0
  359. package/dist/server/ai/providers/KieImage.js +50 -0
  360. package/dist/server/ai/providers/KieImage.js.map +1 -0
  361. package/dist/server/ai/providers/OpenAIText.d.ts +3 -0
  362. package/dist/server/ai/providers/OpenAIText.d.ts.map +1 -0
  363. package/dist/server/ai/providers/OpenAIText.js +103 -0
  364. package/dist/server/ai/providers/OpenAIText.js.map +1 -0
  365. package/dist/server/ai/providers/Together.d.ts +3 -0
  366. package/dist/server/ai/providers/Together.d.ts.map +1 -0
  367. package/dist/server/ai/providers/Together.js +99 -0
  368. package/dist/server/ai/providers/Together.js.map +1 -0
  369. package/dist/server/ai/providers/TogetherImage.d.ts +3 -0
  370. package/dist/server/ai/providers/TogetherImage.d.ts.map +1 -0
  371. package/dist/server/ai/providers/TogetherImage.js +24 -0
  372. package/dist/server/ai/providers/TogetherImage.js.map +1 -0
  373. package/dist/server/ai/providers/Wavespeed.d.ts +3 -0
  374. package/dist/server/ai/providers/Wavespeed.d.ts.map +1 -0
  375. package/dist/server/ai/providers/Wavespeed.js +53 -0
  376. package/dist/server/ai/providers/Wavespeed.js.map +1 -0
  377. package/dist/server/ai/registry.d.ts +10 -0
  378. package/dist/server/ai/registry.d.ts.map +1 -0
  379. package/dist/server/ai/registry.js +26 -0
  380. package/dist/server/ai/registry.js.map +1 -0
  381. package/dist/server/ai/types.d.ts +67 -0
  382. package/dist/server/ai/types.d.ts.map +1 -0
  383. package/dist/server/ai/types.js +17 -0
  384. package/dist/server/ai/types.js.map +1 -0
  385. package/dist/server/audio/STTStream.d.ts +12 -0
  386. package/dist/server/audio/STTStream.d.ts.map +1 -0
  387. package/dist/server/audio/STTStream.js +59 -0
  388. package/dist/server/audio/STTStream.js.map +1 -0
  389. package/dist/server/audio/TTSStream.d.ts +16 -0
  390. package/dist/server/audio/TTSStream.d.ts.map +1 -0
  391. package/dist/server/audio/TTSStream.js +151 -0
  392. package/dist/server/audio/TTSStream.js.map +1 -0
  393. package/dist/server/audio/index.d.ts +5 -0
  394. package/dist/server/audio/index.d.ts.map +1 -0
  395. package/dist/server/audio/index.js +14 -0
  396. package/dist/server/audio/index.js.map +1 -0
  397. package/dist/server/audio/resample.d.ts +7 -0
  398. package/dist/server/audio/resample.d.ts.map +1 -0
  399. package/dist/server/audio/resample.js +17 -0
  400. package/dist/server/audio/resample.js.map +1 -0
  401. package/dist/server/audio/stt/GroqWhisper.d.ts +3 -0
  402. package/dist/server/audio/stt/GroqWhisper.d.ts.map +1 -0
  403. package/dist/server/audio/stt/GroqWhisper.js +91 -0
  404. package/dist/server/audio/stt/GroqWhisper.js.map +1 -0
  405. package/dist/server/audio/stt/registry.d.ts +5 -0
  406. package/dist/server/audio/stt/registry.d.ts.map +1 -0
  407. package/dist/server/audio/stt/registry.js +11 -0
  408. package/dist/server/audio/stt/registry.js.map +1 -0
  409. package/dist/server/audio/stt/types.d.ts +25 -0
  410. package/dist/server/audio/stt/types.d.ts.map +1 -0
  411. package/dist/server/audio/stt/types.js +2 -0
  412. package/dist/server/audio/stt/types.js.map +1 -0
  413. package/dist/server/audio/stt/vad.d.ts +18 -0
  414. package/dist/server/audio/stt/vad.d.ts.map +1 -0
  415. package/dist/server/audio/stt/vad.js +90 -0
  416. package/dist/server/audio/stt/vad.js.map +1 -0
  417. package/dist/server/audio/tts/InWorld.d.ts +3 -0
  418. package/dist/server/audio/tts/InWorld.d.ts.map +1 -0
  419. package/dist/server/audio/tts/InWorld.js +147 -0
  420. package/dist/server/audio/tts/InWorld.js.map +1 -0
  421. package/dist/server/audio/tts/providers/Azure.d.ts +3 -0
  422. package/dist/server/audio/tts/providers/Azure.d.ts.map +1 -0
  423. package/dist/server/audio/tts/providers/Azure.js +31 -0
  424. package/dist/server/audio/tts/providers/Azure.js.map +1 -0
  425. package/dist/server/audio/tts/registry.d.ts +6 -0
  426. package/dist/server/audio/tts/registry.d.ts.map +1 -0
  427. package/dist/server/audio/tts/registry.js +12 -0
  428. package/dist/server/audio/tts/registry.js.map +1 -0
  429. package/dist/server/audio/tts/types.d.ts +20 -0
  430. package/dist/server/audio/tts/types.d.ts.map +1 -0
  431. package/dist/server/audio/tts/types.js +2 -0
  432. package/dist/server/audio/tts/types.js.map +1 -0
  433. package/dist/server/billing/BillingGateway.d.ts +40 -0
  434. package/dist/server/billing/BillingGateway.d.ts.map +1 -0
  435. package/dist/server/billing/BillingGateway.js +116 -0
  436. package/dist/server/billing/BillingGateway.js.map +1 -0
  437. package/dist/server/billing/BillingLedger.d.ts +67 -0
  438. package/dist/server/billing/BillingLedger.d.ts.map +1 -0
  439. package/dist/server/billing/BillingLedger.js +214 -0
  440. package/dist/server/billing/BillingLedger.js.map +1 -0
  441. package/dist/server/billing/CreditStore.d.ts +58 -0
  442. package/dist/server/billing/CreditStore.d.ts.map +1 -0
  443. package/dist/server/billing/CreditStore.js +112 -0
  444. package/dist/server/billing/CreditStore.js.map +1 -0
  445. package/dist/server/billing/LimitEnforcer.d.ts +73 -0
  446. package/dist/server/billing/LimitEnforcer.d.ts.map +1 -0
  447. package/dist/server/billing/LimitEnforcer.js +276 -0
  448. package/dist/server/billing/LimitEnforcer.js.map +1 -0
  449. package/dist/server/billing/UserLimitCache.d.ts +35 -0
  450. package/dist/server/billing/UserLimitCache.d.ts.map +1 -0
  451. package/dist/server/billing/UserLimitCache.js +91 -0
  452. package/dist/server/billing/UserLimitCache.js.map +1 -0
  453. package/dist/server/billing/index.d.ts +6 -0
  454. package/dist/server/billing/index.d.ts.map +1 -0
  455. package/dist/server/billing/index.js +4 -0
  456. package/dist/server/billing/index.js.map +1 -0
  457. package/dist/server/billing/types.d.ts +95 -0
  458. package/dist/server/billing/types.d.ts.map +1 -0
  459. package/dist/server/billing/types.js +19 -0
  460. package/dist/server/billing/types.js.map +1 -0
  461. package/dist/server/embeddings/EmbeddingClient.d.ts +8 -0
  462. package/dist/server/embeddings/EmbeddingClient.d.ts.map +1 -0
  463. package/dist/server/embeddings/EmbeddingClient.js +30 -0
  464. package/dist/server/embeddings/EmbeddingClient.js.map +1 -0
  465. package/dist/server/embeddings/index.d.ts +5 -0
  466. package/dist/server/embeddings/index.d.ts.map +1 -0
  467. package/dist/server/embeddings/index.js +6 -0
  468. package/dist/server/embeddings/index.js.map +1 -0
  469. package/dist/server/embeddings/providers/OpenAI.d.ts +3 -0
  470. package/dist/server/embeddings/providers/OpenAI.d.ts.map +1 -0
  471. package/dist/server/embeddings/providers/OpenAI.js +28 -0
  472. package/dist/server/embeddings/providers/OpenAI.js.map +1 -0
  473. package/dist/server/embeddings/registry.d.ts +7 -0
  474. package/dist/server/embeddings/registry.d.ts.map +1 -0
  475. package/dist/server/embeddings/registry.js +15 -0
  476. package/dist/server/embeddings/registry.js.map +1 -0
  477. package/dist/server/embeddings/types.d.ts +12 -0
  478. package/dist/server/embeddings/types.d.ts.map +1 -0
  479. package/dist/server/embeddings/types.js +2 -0
  480. package/dist/server/embeddings/types.js.map +1 -0
  481. package/dist/server/index.d.ts +59 -0
  482. package/dist/server/index.d.ts.map +1 -0
  483. package/dist/server/index.js +41 -0
  484. package/dist/server/index.js.map +1 -0
  485. package/dist/shared/Api.d.ts +40 -0
  486. package/dist/shared/Api.d.ts.map +1 -0
  487. package/dist/shared/Api.js +30 -0
  488. package/dist/shared/Api.js.map +1 -0
  489. package/dist/shared/Audio.d.ts +108 -0
  490. package/dist/shared/Audio.d.ts.map +1 -0
  491. package/dist/shared/Audio.js +3 -0
  492. package/dist/shared/Audio.js.map +1 -0
  493. package/dist/shared/DB.d.ts +339 -0
  494. package/dist/shared/DB.d.ts.map +1 -0
  495. package/dist/shared/DB.js +51 -0
  496. package/dist/shared/DB.js.map +1 -0
  497. package/dist/shared/Errors.d.ts +10 -0
  498. package/dist/shared/Errors.d.ts.map +1 -0
  499. package/dist/shared/Errors.js +19 -0
  500. package/dist/shared/Errors.js.map +1 -0
  501. package/dist/shared/FeedbackReport.d.ts +38 -0
  502. package/dist/shared/FeedbackReport.d.ts.map +1 -0
  503. package/dist/shared/FeedbackReport.js +14 -0
  504. package/dist/shared/FeedbackReport.js.map +1 -0
  505. package/dist/shared/Router.d.ts +28 -0
  506. package/dist/shared/Router.d.ts.map +1 -0
  507. package/dist/shared/Router.js +82 -0
  508. package/dist/shared/Router.js.map +1 -0
  509. package/dist/shared/Socket.d.ts +26 -0
  510. package/dist/shared/Socket.d.ts.map +1 -0
  511. package/dist/shared/Socket.js +2 -0
  512. package/dist/shared/Socket.js.map +1 -0
  513. package/dist/shared/index.d.ts +28 -0
  514. package/dist/shared/index.d.ts.map +1 -0
  515. package/dist/shared/index.js +61 -0
  516. package/dist/shared/index.js.map +1 -0
  517. package/dist/webrtc/index.d.ts +14 -0
  518. package/dist/webrtc/index.d.ts.map +1 -0
  519. package/dist/webrtc/index.js +24 -0
  520. package/dist/webrtc/index.js.map +1 -0
  521. package/package.json +97 -0
  522. package/templates/.claude/skills/assets.md +39 -0
  523. package/templates/.claude/skills/check-errors.md +35 -0
  524. package/templates/.claude/skills/check-feedback.md +23 -0
  525. package/templates/.claude/skills/check-perf.md +22 -0
  526. package/templates/.claude/skills/create-test-users.md +34 -0
  527. package/templates/.claude/skills/extend-api.md +37 -0
  528. package/templates/.claude/skills/fix-code.md +5 -0
  529. package/templates/.claude/skills/fix-errors.md +9 -0
  530. package/templates/.claude/skills/fix-feedback.md +8 -0
  531. package/templates/.claude/skills/fix-perf.md +8 -0
  532. package/templates/.claude/skills/uploads.md +38 -0
  533. package/templates/.claude/skills/use-ai.md +49 -0
  534. package/templates/.env.example +49 -0
  535. package/templates/.husky/pre-commit +2 -0
  536. package/templates/CLAUDE.md +199 -0
  537. package/templates/client/App.tsx +33 -0
  538. package/templates/client/index.html +14 -0
  539. package/templates/client/main.tsx +52 -0
  540. package/templates/client/pages/AuthDemoPage.tsx +85 -0
  541. package/templates/client/pages/HomePage.tsx +111 -0
  542. package/templates/client/pages/SearchPage.tsx +48 -0
  543. package/templates/client/pages/UserPage.tsx +33 -0
  544. package/templates/client/router.ts +4 -0
  545. package/templates/docker-compose.yml +50 -0
  546. package/templates/index.html +14 -0
  547. package/templates/package.json +56 -0
  548. package/templates/playwright.config.ts +21 -0
  549. package/templates/server/index.ts +24 -0
  550. package/templates/shared/api.ts +36 -0
  551. package/templates/shared/collections.ts +11 -0
  552. package/templates/shared/dbIndexes.ts +11 -0
  553. package/templates/shared/pages.ts +8 -0
  554. package/templates/vite.config.ts +17 -0
package/README.md ADDED
@@ -0,0 +1,709 @@
1
+ # website-core
2
+
3
+ A full-stack TypeScript framework for building production-ready web applications. Provides an opinionated architecture combining an Express backend, React frontend, and MongoDB database with built-in authentication, real-time communication, storage, AI integration, and audio streaming.
4
+
5
+ ## What's Included
6
+
7
+ - **Server**: Express with type-safe RPC routing and Zod schema validation
8
+ - **WebSockets**: Bidirectional socket server/client with RPC calls, document tracking, and file uploads
9
+ - **Database**: MongoDB with typed collections, cascade delete, indexes, migrations, and NATS-based real-time document tracking
10
+ - **Auth**: JWT + HttpOnly cookie sessions, OAuth (ugly.bot by default, extensible via `AuthProvider` interface)
11
+ - **Storage**: AWS S3 / Cloudflare R2 with presigned URLs and file promotion
12
+ - **AI**: 7 text generation providers (Together, Claude, OpenAI, Google, Groq, Fireworks, Kie) + 5 image providers (Together, FAL, Google, Kie, Wavespeed)
13
+ - **Audio**: Text-to-speech and speech-to-text streaming with React hooks (`useTTS`, `useSTT`), optional viseme data for lip sync, and word-level timestamps
14
+ - **Logging**: Multi-channel logging to MongoDB (error, console, perf, feedback) with server error capture, deduplication, and classification
15
+ - **Queues**: NATS JetStream worker queues, Redis pub/sub with in-memory fallback
16
+ - **Billing**: AI spend tracking with global/provider/per-user rolling limits (hourly/daily/weekly), threshold callbacks, profit margin markup, pre-paid user credits, full audit history, and 5-minute DB reconciliation
17
+ - **Email**: Transactional email via Mailgun with Handlebars template support
18
+ - **Rate Limiting**: Per-user/per-operation token-bucket limiting with queue management
19
+ - **Push Notifications**: Real-time delivery via WebSocket + Redis, with FCM support
20
+ - **Feedback**: Built-in user feedback collection with screenshot capture
21
+ - **CLI**: `web` command for dev, build, deploy, migrations, log queries, and auth utilities
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install website-core
27
+ ```
28
+
29
+ Peer dependencies:
30
+
31
+ ```bash
32
+ npm install react react-dom html2canvas
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Scaffold a new project
38
+
39
+ ```bash
40
+ npx web init my-app
41
+ ```
42
+
43
+ ### Start development
44
+
45
+ ```bash
46
+ yarn dev
47
+ # Starts Docker services, Express server, Vite, TypeScript watcher, and ESLint concurrently
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ### Server
53
+
54
+ ```typescript
55
+ import { createApp, createUserHelper, registerFeedbackHandlers } from 'website-core';
56
+ import { dbDefaults } from 'website-core/shared';
57
+ import { functions, requests } from '../shared/api.js';
58
+ import { collections } from '../shared/collections.js';
59
+ import { pages } from '../shared/pages.js';
60
+ import type { User } from '../shared/types.js';
61
+
62
+ export const userHelper = createUserHelper(collections.user);
63
+
64
+ const app = createApp({ functions, requests }, collections, (config) => {
65
+ config.setPages({ pages });
66
+
67
+ config.setUserHelper(userHelper); // required
68
+
69
+ config.setOnUserCreate(async (userId, info, db) => { // required
70
+ await userHelper.set(db, {
71
+ ...dbDefaults(),
72
+ id: userId,
73
+ email: info.email,
74
+ phone: info.phone,
75
+ });
76
+ });
77
+
78
+ // Optional
79
+ // config.setAuth(myAuthProvider);
80
+ // config.setContextExtensions(async (base) => ({ nats: await connectNats() }));
81
+ });
82
+
83
+ app.registerRequest('getMe', async (ctx) => {
84
+ const user = await userHelper.get(ctx.db, ctx.userId);
85
+ return { userId: ctx.userId, email: user?.email };
86
+ });
87
+
88
+ registerFeedbackHandlers(app, process.env.MAINTAIN_BOT_USER_ID ?? '');
89
+
90
+ const port = parseInt(process.env['PORT'] ?? '3000');
91
+ await app.start(port);
92
+ ```
93
+
94
+ > Both `setUserHelper` and `setOnUserCreate` are **required** — `createApp` throws at startup if either is missing.
95
+
96
+ ### Shared types and API definitions
97
+
98
+ ```typescript
99
+ import { fn, req, defineFunctions, defineRequests } from 'website-core/shared';
100
+ import { z } from 'website-core/shared';
101
+
102
+ export const functions = defineFunctions({
103
+ greet: fn(
104
+ z.object({ name: z.string() }),
105
+ z.object({ message: z.string() })
106
+ ),
107
+ });
108
+ ```
109
+
110
+ ### React client
111
+
112
+ ```typescript
113
+ import { AppProvider, useApp, createRouter, createSocket } from 'website-core/client';
114
+ ```
115
+
116
+ ## Key APIs
117
+
118
+ ### Server — `createApp()`
119
+
120
+ ```typescript
121
+ createApp(registry, appDefs, configure?)
122
+ ```
123
+
124
+ - `registry` — `{ functions, requests }` from your shared API definitions
125
+ - `appDefs` — collection defs from `defineCollections()`
126
+ - `configure` — optional callback receiving an `AppConfigurator`:
127
+
128
+ ```typescript
129
+ interface AppConfigurator {
130
+ setUserHelper(helper: UserHelper<any>): void; // required
131
+ setOnUserCreate(handler: OnUserCreate): void; // required
132
+ setAuth(provider: AuthProvider): void;
133
+ setPages(options: PageServerOptions): void;
134
+ setOnSocketMessage(handler: (ws, userId, msg) => boolean): void;
135
+ setContextExtensions<E>(fn: (base: HandlerContext) => Promise<E>): void;
136
+ setWorkerQueue(queue: WorkerQueue): void;
137
+ registerRoutes(fn: (router: express.Router) => void): void;
138
+ }
139
+ ```
140
+
141
+ Registers handlers with full context (`userId`, `db`, `storage`, `textGen`, `imageGen`, `log`, `rateLimit`):
142
+
143
+ ```typescript
144
+ app.registerFunction('greet', async (ctx, input) => {
145
+ ctx.log.info('Greeting user', { userId: ctx.userId });
146
+ return { message: `Hello, ${input.name}` };
147
+ });
148
+
149
+ app.registerRequest('stream', async (ctx, input) => {
150
+ // streaming HTTP handler — ctx.res available for request handlers
151
+ });
152
+ ```
153
+
154
+ Built-in routes: `GET /health`, `POST /auth/verify`, `GET /auth/token`, `POST /auth/logout`, `GET /auth/url`, `POST /logs/client`, `GET /my_feedback`
155
+
156
+ ### Auth — Cookie Sessions
157
+
158
+ After login, the server sets an `auth_token` HttpOnly cookie. On every page load the server refreshes it and injects the token into the HTML:
159
+
160
+ ```html
161
+ <script>window.__AUTH_TOKEN__ = "eyJ..."</script>
162
+ ```
163
+
164
+ The client reads `window.__AUTH_TOKEN__` synchronously — no localStorage, no extra HTTP round-trip. To log out:
165
+
166
+ ```typescript
167
+ await fetch('/auth/logout', { method: 'POST' });
168
+ window.location.reload();
169
+ ```
170
+
171
+ ### Database — `TypedDB`
172
+
173
+ Define collections in `shared/collections.ts`. The `defineCollections` call injects the collection name into each def, making the def itself the collection reference:
174
+
175
+ ```typescript
176
+ import { defineCollections } from 'website-core/shared';
177
+
178
+ export const collections = defineCollections({
179
+ notes: {
180
+ type: {} as Note,
181
+ meta: { cache: true, trackable: true, public: false, parent: null },
182
+ onDelete: async (doc, db) => { /* cleanup child docs */ },
183
+ },
184
+ comments: {
185
+ type: {} as Comment,
186
+ meta: { cache: false, trackable: true, public: false, parent: 'notes' },
187
+ },
188
+ });
189
+ ```
190
+
191
+ Inside handlers, use `ctx.db` — a `TypedDB` instance pre-created by `createApp`. All methods take the collection def as the first argument (not a string name):
192
+
193
+ ```typescript
194
+ // Insert / replace
195
+ await ctx.db.setDoc(collections.notes, doc);
196
+ await ctx.db.setDoc(collections.notes, doc, { skipIfExists: true }); // INSERT only
197
+
198
+ // Partial updates (dot-notation, sets `updated` automatically)
199
+ await ctx.db.setDocFields(collections.notes, id, { title: 'New title' });
200
+ const doc = await ctx.db.setDocFieldsOrIgnore(collections.notes, id, { title }); // null if missing
201
+ const doc = await ctx.db.setDocFieldsOrCreate(collections.notes, id, fields, fullObj);
202
+
203
+ // MongoDB update operators ($inc, $addToSet, $pull, $unset, $set)
204
+ await ctx.db.setDocOp(collections.notes, id, { $inc: { viewCount: 1 } });
205
+ const doc = await ctx.db.setDocOpOrIgnore(collections.notes, id, { $addToSet: { tags: 'foo' } });
206
+
207
+ // Reads
208
+ const note = await ctx.db.getDoc(collections.notes, id);
209
+ const notes = await ctx.db.getDocs(collections.notes, { userId }, { sort: { created: -1 }, limit: 20, skip: 0 });
210
+
211
+ // Aggregate pipeline queries
212
+ const results = await ctx.db.getQuery<MyResult>('notes', pipeline, { skip, limit });
213
+ const count = await ctx.db.getQueryCount('notes', pipeline);
214
+ const raw = await ctx.db.getQueryRaw<RawDoc>('notes', pipeline); // no id remapping
215
+
216
+ // Delete with cascade
217
+ await ctx.db.deleteDoc(collections.notes, id);
218
+ await ctx.db.deleteQuery(collections.notes, { userId });
219
+
220
+ // Nested / child collections (parentId compound _id: "parentId:leafId")
221
+ await ctx.db.setDoc(collections.comments, comment, { parentId: noteId });
222
+ await ctx.db.getDocs(collections.comments, {}, { parentId: noteId });
223
+ await ctx.db.deleteDoc(collections.comments, commentId, { parentId: noteId });
224
+
225
+ // Direct cache access
226
+ const key = ctx.db.cacheKey('notes', id);
227
+ ctx.db.cacheSet(key, value, 60_000);
228
+ const cached = ctx.db.cacheGet<Note>(key);
229
+ ctx.db.cacheDelete(key);
230
+ ```
231
+
232
+ Per-collection `cache: true` enables automatic LRU caching on `getDoc` / `setDoc` / `deleteDoc`. Use `parent: 'collectionName'` to define parent–child relationships for compound `_id`s and cascade deletes.
233
+
234
+ ### WebSocket Client
235
+
236
+ ```typescript
237
+ const socket = createSocket({ functions, requests });
238
+
239
+ await socket.connect(token); // must call first
240
+ await socket.call('greet', { name: 'world' }); // function RPC
241
+ await socket.request('getMe', {}); // request RPC
242
+ const doc = await socket.getDoc('notes', id); // fetch document
243
+ socket.trackDoc('notes', id, (doc) => { /* live */ }); // live updates
244
+ await socket.uploadFile(file, key); // file upload (key is a string)
245
+ ```
246
+
247
+ `trackDocs` accepts optional `sort`, `limit`, and `skip` parameters for server-side ordering and pagination:
248
+
249
+ ```typescript
250
+ // Live-updating query: most recent 20 notes, newest first
251
+ socket.trackDocs('notes', { userId }, (docs) => { /* live */ }, {
252
+ sort: { created: -1 },
253
+ limit: 20,
254
+ skip: 0,
255
+ });
256
+ ```
257
+
258
+ `getDocs` on the server also accepts the same options:
259
+
260
+ ```typescript
261
+ const docs = await ctx.db.getDocs(collections.notes, { userId }, {
262
+ sort: { created: -1 },
263
+ limit: 20,
264
+ skip: 0,
265
+ });
266
+ ```
267
+
268
+ ### AI — Text Generation
269
+
270
+ ```typescript
271
+ const text = await ctx.textGen.generate(messages, { model });
272
+ const json = await ctx.textGen.generateJson(schema, messages, { model });
273
+ const result = await ctx.textGen.generateWithTools(messages, tools, { model });
274
+ ```
275
+
276
+ Supported providers: `together`, `claude`, `openai`, `google`, `groq`, `fireworks`, `kie`
277
+
278
+ ### AI — Image Generation
279
+
280
+ ```typescript
281
+ const url = await ctx.imageGen.generate(prompt, { width: 1024, height: 1024 });
282
+ ```
283
+
284
+ Supported providers: `together`, `fal`, `google`, `kie`, `wavespeed`
285
+
286
+ ### Audio — TTS/STT React Hooks
287
+
288
+ ```typescript
289
+ // Text-to-speech
290
+ const { playing, currentWord, play, stop } = useTTS(socket);
291
+ await play('Hello world');
292
+
293
+ // With viseme data for lip sync (opt-in — adds latency)
294
+ await play('Hello world', { requestVisemes: true });
295
+ // viseme events arrive via onViseme callback in useTTS options
296
+
297
+ // Speech-to-text
298
+ const { transcript, isFinal, listening, start, stop } = useSTT(socket, {
299
+ mode: 'realtime', // 'realtime' | 'batch' | 'auto'
300
+ });
301
+
302
+ // With word-level timestamps (opt-in — uses Groq verbose_json, slower)
303
+ const { transcript, words } = useSTT(socket, {
304
+ mode: 'batch',
305
+ requestWords: true,
306
+ });
307
+ // words: STTWord[] — each has { word, startMs, durationMs }
308
+ ```
309
+
310
+ Viseme and word types are exported from `website-core/shared`:
311
+
312
+ ```typescript
313
+ import type { TTSViseme, TTSVisemeName, STTWord } from 'website-core/shared';
314
+ ```
315
+
316
+ ### Email
317
+
318
+ Send transactional email via Mailgun:
319
+
320
+ ```typescript
321
+ import { sendEmail, loadEmailTemplate, sendTemplateEmail } from 'website-core';
322
+
323
+ // One-off HTML email
324
+ await sendEmail({
325
+ to: 'user@example.com',
326
+ subject: 'Welcome!',
327
+ html: '<p>Thanks for signing up.</p>',
328
+ from: 'noreply@my-app.com', // optional — defaults to MAILGUN_FROM env var
329
+ replyTo: 'support@my-app.com',
330
+ headers: { 'X-Campaign-Id': 'welcome' },
331
+ });
332
+
333
+ // Handlebars template email
334
+ interface WelcomeData { name: string; confirmUrl: string }
335
+ const welcomeTemplate = loadEmailTemplate<WelcomeData>('/path/to/welcome.hbs');
336
+
337
+ await sendTemplateEmail('user@example.com', 'Confirm your email', welcomeTemplate, {
338
+ name: 'Alice',
339
+ confirmUrl: 'https://my-app.com/confirm?token=abc',
340
+ });
341
+ ```
342
+
343
+ Required environment variables: `MAILGUN_API_KEY`, `MAILGUN_DOMAIN`, `MAILGUN_FROM`. If `MAILGUN_API_KEY` is not set, sending is silently skipped (dev-friendly).
344
+
345
+ ### Billing
346
+
347
+ Track AI spend and enforce rate limits across all cost types (`textGen`, `imageGen`, `stt`, `tts`, `embedding`, `service`):
348
+
349
+ ```typescript
350
+ import {
351
+ initBillingGateway,
352
+ getBillingGateway,
353
+ BillingLimitError,
354
+ BillingValidationError,
355
+ } from 'website-core';
356
+
357
+ // Initialize once at startup (before serving traffic)
358
+ const billing = initBillingGateway(db, {
359
+ global: {
360
+ hourlyUsd: 50, // hard cap across all providers/users
361
+ thresholdPct: 0.8, // fire callback at 80%
362
+ },
363
+ providers: {
364
+ openai: { hourlyUsd: 20, thresholdPct: 0.9 },
365
+ anthropic: { hourlyUsd: 30 },
366
+ },
367
+ profitMarginPct: 0.5, // optional: 50% markup applied before limit checks
368
+ reconcileIntervalMs: 300_000, // flush to DB every 5 min (default)
369
+ userLimitCacheSize: 10_000, // LRU entries (default)
370
+ }, creditDb); // optional CreditDB for pre-paid credits
371
+
372
+ // Supply per-user limits from your database
373
+ billing.setUserLimitHook(async (userId) => {
374
+ const plan = await db.getDoc(collections.plans, userId);
375
+ return plan
376
+ ? { hourlyUsd: plan.hourlyUsd, dailyUsd: plan.dailyUsd, weeklyUsd: plan.weeklyUsd,
377
+ thresholds: { daily: 0.9 } }
378
+ : null; // null = no limits
379
+ });
380
+
381
+ // Alert callbacks (errors swallowed — never block a charge)
382
+ billing.setGlobalThresholdCallback(({ limitType, spent, limit, thresholdPct, provider }) => {
383
+ console.warn(`[Billing] ${limitType} at ${Math.round(thresholdPct * 100)}%: $${spent}/$${limit}`);
384
+ });
385
+ billing.setUserThresholdCallback(({ userId, limitType, spent, limit }) => {
386
+ void notifyUser(userId, `You've used ${Math.round(spent / limit * 100)}% of your ${limitType} budget`);
387
+ });
388
+ ```
389
+
390
+ **Recording a charge:**
391
+
392
+ ```typescript
393
+ // Returns BillingRecord on success; throws on limit violation
394
+ try {
395
+ const record = await getBillingGateway().charge({
396
+ userId, // optional — omit for anonymous charges
397
+ provider: 'openai',
398
+ model: 'gpt-4o',
399
+ type: 'textGen', // 'textGen' | 'imageGen' | 'stt' | 'tts' | 'embedding' | 'service'
400
+ costUsd: 0.012,
401
+ inputTokens: 800,
402
+ outputTokens: 400,
403
+ });
404
+ } catch (err) {
405
+ if (err instanceof BillingLimitError) {
406
+ // err.limitType: 'global-hourly' | 'provider-hourly' | 'user-hourly' | 'user-daily' | 'user-weekly'
407
+ // err.spent, err.limit
408
+ }
409
+ }
410
+ ```
411
+
412
+ **Querying spend and history:**
413
+
414
+ ```typescript
415
+ const billing = getBillingGateway();
416
+
417
+ // Rolling window totals
418
+ const globalHour = await billing.getWindowSpend(3_600_000);
419
+ const userDay = await billing.getWindowSpend(86_400_000, { userId });
420
+ const openaiHour = await billing.getWindowSpend(3_600_000, { provider: 'openai' });
421
+
422
+ // Audit history (default limit 100, max 1000)
423
+ const records = await billing.getHistory({ userId, limit: 50, skip: 0 });
424
+ ```
425
+
426
+ **Cache invalidation** (call after a user's plan changes):
427
+
428
+ ```typescript
429
+ billing.invalidateUserLimit(userId);
430
+ billing.invalidateAllUserLimits();
431
+ ```
432
+
433
+ **Pre-paid credits** (optional — requires `creditDb` passed to `initBillingGateway`):
434
+
435
+ When a user would otherwise be blocked by a rate limit, the gateway automatically deducts from their credit balance as a fallback. Global and provider limits are never bypassed.
436
+
437
+ ```typescript
438
+ import { CreditStore, MongoCreditDB } from 'website-core';
439
+
440
+ // Set up the credit DB (uses your TypedDB instance)
441
+ const creditDb = new MongoCreditDB(db.userCredits);
442
+ const billing = initBillingGateway(ledgerDb, config, creditDb);
443
+
444
+ // Grant credits (e.g. after a purchase)
445
+ await billing.grantCredit(userId, 5.00); // $5.00
446
+
447
+ // Check balance
448
+ const balance = await billing.getCreditBalance(userId);
449
+
450
+ // Invalidate cached balance (e.g. after external grant)
451
+ billing.invalidateCreditCache(userId);
452
+ ```
453
+
454
+ Charges are recorded to an `aiSpend` MongoDB collection and reconciled in-memory every 5 minutes with a forced flush on `shutdown()`. Each text and image generation call from `createTextGen`/`createImageGen` is automatically charged through the gateway when initialized.
455
+
456
+ ### Rate Limiting
457
+
458
+ ```typescript
459
+ const rateLimit = createRateLimiter({
460
+ windowSeconds: 3600, // 1 hour (default)
461
+ maxPerWindow: 1.0, // max units per window (default)
462
+ maxQueueDepth: 100, // max queued requests (default)
463
+ maxWaitMs: 300_000, // max wait time in queue (default)
464
+ });
465
+
466
+ await rateLimit.check(userId, 'generate', 0.1);
467
+ await rateLimit.charge(userId, 'generate', actualCost);
468
+ ```
469
+
470
+ ### Worker Queues
471
+
472
+ ```typescript
473
+ const queue = createWorkerQueue({
474
+ streamName: 'JOBS', // NATS stream name (default: 'JOBS')
475
+ clockServerOnly: true, // only process when IS_CLOCK_SERVER=true (default: true)
476
+ concurrency: 10, // max concurrent jobs (default: 10)
477
+ maxRetries: 3, // max retry attempts (default: 3)
478
+ });
479
+
480
+ await queue.enqueue('email', { to: 'user@example.com' });
481
+ await queue.enqueue('email', payload, { delay: 5000 }); // delay in ms
482
+ queue.registerHandler('email', async (job) => { /* job.payload, job.working() */ });
483
+ await queue.start();
484
+ ```
485
+
486
+ ### Storage
487
+
488
+ ```typescript
489
+ const storage = createStorageClient();
490
+
491
+ // Server-side upload
492
+ const url = await storage.put('temp', key, buffer, 'image/png');
493
+ const publicUrl = await storage.moveToPublic(tempKey, destKey);
494
+ const url = storage.url('public', destKey);
495
+
496
+ // Presigned URL for browser direct upload
497
+ const { uploadUrl, resultUrl } = await storage.presignedPut('temp', key);
498
+ ```
499
+
500
+ ### Logging
501
+
502
+ ```typescript
503
+ // In handlers, use ctx.log
504
+ ctx.log.info('User signed in', { userId });
505
+ ctx.log.warn('Unexpected state', { context });
506
+ ctx.log.error('Something failed', { error: err.message });
507
+
508
+ // Performance timing — returns a stop function
509
+ const stop = ctx.log.perf('db-query');
510
+ // ... do work ...
511
+ stop(); // records duration to MongoDB
512
+ ```
513
+
514
+ ### Server Error Capture
515
+
516
+ `captureServerError` persists unexpected errors to a MongoDB `errorLog` collection with automatic deduplication (repeated errors increment a `count` field instead of inserting duplicates). Expected/recoverable errors can be suppressed from persistence by registering patterns.
517
+
518
+ ```typescript
519
+ import {
520
+ captureServerError,
521
+ registerExpectedErrorPattern,
522
+ classifyError,
523
+ } from 'website-core';
524
+
525
+ // Register known-recoverable patterns at startup — matched errors log to
526
+ // console only and are never written to MongoDB
527
+ registerExpectedErrorPattern('ECONNRESET')
528
+ registerExpectedErrorPattern('[STT] Session ended before audio was received')
529
+
530
+ // Capture an error in a catch block
531
+ try {
532
+ await riskyOperation()
533
+ } catch (err) {
534
+ captureServerError('[MyService] Failed to process request', err, { userId })
535
+ }
536
+
537
+ // Classify a message manually ('expected' | 'unexpected')
538
+ const classification = classifyError('[STT] Session ended before audio was received')
539
+ ```
540
+
541
+ ### Push Notifications
542
+
543
+ ```typescript
544
+ import { sendPush } from 'website-core';
545
+
546
+ await sendPush(userId, { title: 'New message', body: 'You have a reply' });
547
+ ```
548
+
549
+ ## CLI Commands
550
+
551
+ | Command | Description |
552
+ |---|---|
553
+ | `web init <name>` | Scaffold a new project |
554
+ | `web dev` | Start all dev services (Docker, server, Vite, TS, ESLint) |
555
+ | `web build` | Production build |
556
+ | `web db:init` | Create/update MongoDB indexes |
557
+ | `web db:migrate` | Run pending database migrations (`--status` to preview) |
558
+ | `web publish:assets` | Deploy static assets to CDN (`--dry-run` to preview) |
559
+ | `web purge:assets` | Remove old build artifacts (keeps last 3 by default) |
560
+ | `web logs:local` | Query local dev logs |
561
+ | `web logs:server` | Query server logs from MongoDB |
562
+ | `web error:local` | Query local error logs |
563
+ | `web error:server` | Query server error logs from MongoDB |
564
+ | `web perf:local` | Query local performance metrics |
565
+ | `web perf:server` | Query server performance metrics from MongoDB |
566
+ | `web feedback` | Query user feedback submissions |
567
+ | `web auth:create-account` | Create an account directly in the database |
568
+ | `web auth:create-token` | Generate a JWT for a userId |
569
+ | `web test:e2e` | Run Playwright end-to-end tests (`--headed` for browser UI) |
570
+
571
+ ## React Components
572
+
573
+ ```typescript
574
+ import {
575
+ Button, Card, Text, Input, Modal, Toast, PageLayout,
576
+ FeedbackButton,
577
+ } from 'website-core/client';
578
+ ```
579
+
580
+ The `FeedbackButton` captures a screenshot and submits it with user context automatically.
581
+
582
+ ## Routing (Client)
583
+
584
+ ```typescript
585
+ const { useRouter, RouterProvider, RouterView } = createRouter(pages);
586
+
587
+ function App() {
588
+ const { push, replace, back } = useRouter();
589
+ return (
590
+ <RouterProvider fallback={<NotFound />} loginFallback={<Login />} isAuthenticated={() => !!userId}>
591
+ <RouterView renderPage={(state) => <PageSwitch route={state.name} params={state.params} />} />
592
+ </RouterProvider>
593
+ );
594
+ }
595
+ ```
596
+
597
+ Page transitions are animated by default using `ViewFlipper`. The transition type (`PUSH`, `POP`, `REPLACE`, `NONE`) is inferred automatically from navigation calls.
598
+
599
+ ### Popups
600
+
601
+ ```typescript
602
+ import { useRouter } from './router';
603
+
604
+ function MyComponent() {
605
+ const { openPopup } = useRouter();
606
+
607
+ function openMenu() {
608
+ const handle = openPopup(<MyMenu />, {
609
+ mode: 'contextMenu', // 'block' | 'transient' | 'contextMenu'
610
+ slideFrom: 'bottom', // 'left' | 'right' | 'top' | 'bottom' | 'none'
611
+ });
612
+
613
+ // Dismiss programmatically
614
+ handle.hide();
615
+ }
616
+ }
617
+ ```
618
+
619
+ - **`block`** — modal overlay, blocks interaction behind it
620
+ - **`transient`** — tapping the backdrop closes it
621
+ - **`contextMenu`** — same as transient but typically for menus/pickers
622
+
623
+ ### Animation Primitives
624
+
625
+ ```typescript
626
+ import {
627
+ createAnimatedValue,
628
+ useAnimatedValue,
629
+ Animated,
630
+ easingFunctions,
631
+ } from 'website-core/client';
632
+ import type { EasingFunction } from 'website-core/client';
633
+
634
+ // Imperative animated value (RAF-driven, no React re-renders)
635
+ const spring = createAnimatedValue(0);
636
+ spring.animateTo(1, { duration: 300, easing: easingFunctions.easeInOut });
637
+
638
+ // React hook version
639
+ const opacity = useAnimatedValue(0);
640
+
641
+ // Apply to DOM via ref — zero re-renders
642
+ <Animated value={spring} style={(v) => ({ opacity: v, transform: `scale(${v})` })}>
643
+ <div>Content</div>
644
+ </Animated>
645
+ ```
646
+
647
+ ## Project Structure
648
+
649
+ Projects built with `website-core` follow this layout:
650
+
651
+ ```
652
+ my-app/
653
+ ├── src/
654
+ │ ├── server/ # Express handlers, DB collections, migrations
655
+ │ ├── client/ # React pages and components
656
+ │ └── shared/ # API definitions and shared types
657
+ ├── static/ # Build-time static assets
658
+ └── docker-compose.yml
659
+ ```
660
+
661
+ ## Environment Variables
662
+
663
+ | Variable | Description |
664
+ |---|---|
665
+ | `MONGODB_URI` | MongoDB connection string |
666
+ | `JWT_SECRET` | Secret for signing JWT tokens |
667
+ | `REDIS_URL` | Redis connection (optional, uses in-memory fallback) |
668
+ | `NATS_URL` | NATS server URL |
669
+ | `NATS_CREDS` | NATS credentials file path (Synadia Cloud) |
670
+ | `STORAGE_ACCOUNT_ID` | Cloudflare R2 account ID (alias: `CLOUDFLARE_ACCOUNT_ID`) |
671
+ | `STORAGE_ACCESS_KEY_ID` | R2 access key (alias: `CLOUDFLARE_R2_ACCESS_KEY_ID`) |
672
+ | `STORAGE_SECRET_ACCESS_KEY` | R2 secret key (alias: `CLOUDFLARE_R2_SECRET_ACCESS_KEY`) |
673
+ | `STORAGE_PUBLIC_BUCKET` | R2 public bucket name (alias: `CLOUDFLARE_R2_PUBLIC_BUCKET`) |
674
+ | `STORAGE_TEMP_BUCKET` | R2 temp bucket name (alias: `CLOUDFLARE_R2_TEMP_BUCKET`) |
675
+ | `STORAGE_PUBLIC_URL` | Base URL for public bucket (alias: `CLOUDFLARE_R2_PUBLIC_URL`) |
676
+ | `STORAGE_TEMP_URL` | Base URL for temp bucket (alias: `CLOUDFLARE_R2_TEMP_URL`) |
677
+ | `TOGETHER_API_KEY` | Together AI API key |
678
+ | `ANTHROPIC_API_KEY` | Anthropic Claude API key |
679
+ | `OPENAI_API_KEY` | OpenAI API key |
680
+ | `GOOGLE_API_KEY` | Google Gemini API key |
681
+ | `IS_CLOCK_SERVER` | Set to `true` on the instance that processes worker queues |
682
+ | `MAILGUN_API_KEY` | Mailgun API key (`key-...`) |
683
+ | `MAILGUN_DOMAIN` | Mailgun sending domain (e.g., `mg.my-app.com`) |
684
+ | `MAILGUN_FROM` | Default from address (e.g., `noreply@my-app.com`) |
685
+
686
+ Client-side variables must be prefixed with `VITE_`.
687
+
688
+ ## Tech Stack
689
+
690
+ - **Runtime**: Node.js, TypeScript (ES2022, NodeNext modules)
691
+ - **Server**: Express, ws (WebSockets)
692
+ - **Frontend**: React 19, Vite, Tailwind CSS
693
+ - **Database**: MongoDB
694
+ - **Messaging**: NATS with JetStream
695
+ - **Cache**: Redis (in-memory fallback for dev)
696
+ - **Storage**: AWS S3 / Cloudflare R2
697
+ - **Auth**: JWT (jose), OAuth
698
+ - **AI**: Together AI, Anthropic, OpenAI, Google, Groq, FAL
699
+ - **Audio**: InWorld TTS, Groq Whisper STT
700
+ - **Validation**: Zod
701
+ - **Testing**: Vitest, Playwright, mongodb-memory-server
702
+
703
+ ## Development
704
+
705
+ ```bash
706
+ npm run build # Compile TypeScript
707
+ npm test # Run unit tests
708
+ npm run test:watch # Watch mode
709
+ ```