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.
- package/.claude/settings.local.json +18 -0
- package/.env.example +9 -0
- package/.yarn-metadata.json +113 -0
- package/README.md +709 -0
- package/dist/cli/authCommands.d.ts +6 -0
- package/dist/cli/authCommands.d.ts.map +1 -0
- package/dist/cli/authCommands.js +50 -0
- package/dist/cli/authCommands.js.map +1 -0
- package/dist/cli/build.d.ts +2 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +26 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/configure.d.ts +2 -0
- package/dist/cli/configure.d.ts.map +1 -0
- package/dist/cli/configure.js +53 -0
- package/dist/cli/configure.js.map +1 -0
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +66 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +340 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/initDb.d.ts +2 -0
- package/dist/cli/initDb.d.ts.map +1 -0
- package/dist/cli/initDb.js +110 -0
- package/dist/cli/initDb.js.map +1 -0
- package/dist/cli/logQuery.d.ts +12 -0
- package/dist/cli/logQuery.d.ts.map +1 -0
- package/dist/cli/logQuery.js +176 -0
- package/dist/cli/logQuery.js.map +1 -0
- package/dist/cli/migrate.d.ts +4 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +62 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/publishAssets.d.ts +6 -0
- package/dist/cli/publishAssets.d.ts.map +1 -0
- package/dist/cli/publishAssets.js +87 -0
- package/dist/cli/publishAssets.js.map +1 -0
- package/dist/cli/purgeAssets.d.ts +2 -0
- package/dist/cli/purgeAssets.d.ts.map +1 -0
- package/dist/cli/purgeAssets.js +36 -0
- package/dist/cli/purgeAssets.js.map +1 -0
- package/dist/cli/scaffold.d.ts +2 -0
- package/dist/cli/scaffold.d.ts.map +1 -0
- package/dist/cli/scaffold.js +37 -0
- package/dist/cli/scaffold.js.map +1 -0
- package/dist/cli/serverLogQuery.d.ts +6 -0
- package/dist/cli/serverLogQuery.d.ts.map +1 -0
- package/dist/cli/serverLogQuery.js +76 -0
- package/dist/cli/serverLogQuery.js.map +1 -0
- package/dist/cli/storageClient.d.ts +3 -0
- package/dist/cli/storageClient.d.ts.map +1 -0
- package/dist/cli/storageClient.js +22 -0
- package/dist/cli/storageClient.js.map +1 -0
- package/dist/cli/uglyappConfig.d.ts +29 -0
- package/dist/cli/uglyappConfig.d.ts.map +1 -0
- package/dist/cli/uglyappConfig.js +125 -0
- package/dist/cli/uglyappConfig.js.map +1 -0
- package/dist/client/AppProvider.d.ts +32 -0
- package/dist/client/AppProvider.d.ts.map +1 -0
- package/dist/client/AppProvider.js +126 -0
- package/dist/client/AppProvider.js.map +1 -0
- package/dist/client/ErrorLog.d.ts +16 -0
- package/dist/client/ErrorLog.d.ts.map +1 -0
- package/dist/client/ErrorLog.js +28 -0
- package/dist/client/ErrorLog.js.map +1 -0
- package/dist/client/FeedbackContext.d.ts +7 -0
- package/dist/client/FeedbackContext.d.ts.map +1 -0
- package/dist/client/FeedbackContext.js +15 -0
- package/dist/client/FeedbackContext.js.map +1 -0
- package/dist/client/Logger.d.ts +2 -0
- package/dist/client/Logger.d.ts.map +1 -0
- package/dist/client/Logger.js +72 -0
- package/dist/client/Logger.js.map +1 -0
- package/dist/client/LoginPopup.d.ts +7 -0
- package/dist/client/LoginPopup.d.ts.map +1 -0
- package/dist/client/LoginPopup.js +55 -0
- package/dist/client/LoginPopup.js.map +1 -0
- package/dist/client/Router.d.ts +56 -0
- package/dist/client/Router.d.ts.map +1 -0
- package/dist/client/Router.js +224 -0
- package/dist/client/Router.js.map +1 -0
- package/dist/client/Screenshot.d.ts +7 -0
- package/dist/client/Screenshot.d.ts.map +1 -0
- package/dist/client/Screenshot.js +54 -0
- package/dist/client/Screenshot.js.map +1 -0
- package/dist/client/ViewFlipper.d.ts +16 -0
- package/dist/client/ViewFlipper.d.ts.map +1 -0
- package/dist/client/ViewFlipper.js +156 -0
- package/dist/client/ViewFlipper.js.map +1 -0
- package/dist/client/animation/Animated.d.ts +14 -0
- package/dist/client/animation/Animated.d.ts.map +1 -0
- package/dist/client/animation/Animated.js +97 -0
- package/dist/client/animation/Animated.js.map +1 -0
- package/dist/client/animation/animatedValue.d.ts +41 -0
- package/dist/client/animation/animatedValue.d.ts.map +1 -0
- package/dist/client/animation/animatedValue.js +123 -0
- package/dist/client/animation/animatedValue.js.map +1 -0
- package/dist/client/animations/FadeIn.d.ts +6 -0
- package/dist/client/animations/FadeIn.d.ts.map +1 -0
- package/dist/client/animations/FadeIn.js +24 -0
- package/dist/client/animations/FadeIn.js.map +1 -0
- package/dist/client/animations/SlideFromBottom.d.ts +6 -0
- package/dist/client/animations/SlideFromBottom.d.ts.map +1 -0
- package/dist/client/animations/SlideFromBottom.js +24 -0
- package/dist/client/animations/SlideFromBottom.js.map +1 -0
- package/dist/client/animations/SlideFromRight.d.ts +6 -0
- package/dist/client/animations/SlideFromRight.d.ts.map +1 -0
- package/dist/client/animations/SlideFromRight.js +24 -0
- package/dist/client/animations/SlideFromRight.js.map +1 -0
- package/dist/client/audio/AudioPlayer.d.ts +11 -0
- package/dist/client/audio/AudioPlayer.d.ts.map +1 -0
- package/dist/client/audio/AudioPlayer.js +51 -0
- package/dist/client/audio/AudioPlayer.js.map +1 -0
- package/dist/client/audio/AudioRecorder.d.ts +11 -0
- package/dist/client/audio/AudioRecorder.d.ts.map +1 -0
- package/dist/client/audio/AudioRecorder.js +66 -0
- package/dist/client/audio/AudioRecorder.js.map +1 -0
- package/dist/client/audio/audioPlayer.worklet.d.ts +2 -0
- package/dist/client/audio/audioPlayer.worklet.d.ts.map +1 -0
- package/dist/client/audio/audioPlayer.worklet.js +46 -0
- package/dist/client/audio/audioPlayer.worklet.js.map +1 -0
- package/dist/client/audio/useSTT.d.ts +19 -0
- package/dist/client/audio/useSTT.d.ts.map +1 -0
- package/dist/client/audio/useSTT.js +91 -0
- package/dist/client/audio/useSTT.js.map +1 -0
- package/dist/client/audio/useTTS.d.ts +17 -0
- package/dist/client/audio/useTTS.d.ts.map +1 -0
- package/dist/client/audio/useTTS.js +70 -0
- package/dist/client/audio/useTTS.js.map +1 -0
- package/dist/client/components/Button.d.ts +22 -0
- package/dist/client/components/Button.d.ts.map +1 -0
- package/dist/client/components/Button.js +27 -0
- package/dist/client/components/Button.js.map +1 -0
- package/dist/client/components/Card.d.ts +8 -0
- package/dist/client/components/Card.d.ts.map +1 -0
- package/dist/client/components/Card.js +6 -0
- package/dist/client/components/Card.js.map +1 -0
- package/dist/client/components/EnumInput.d.ts +16 -0
- package/dist/client/components/EnumInput.d.ts.map +1 -0
- package/dist/client/components/EnumInput.js +11 -0
- package/dist/client/components/EnumInput.js.map +1 -0
- package/dist/client/components/FeedbackButton.d.ts +9 -0
- package/dist/client/components/FeedbackButton.d.ts.map +1 -0
- package/dist/client/components/FeedbackButton.js +112 -0
- package/dist/client/components/FeedbackButton.js.map +1 -0
- package/dist/client/components/Header.d.ts +10 -0
- package/dist/client/components/Header.d.ts.map +1 -0
- package/dist/client/components/Header.js +12 -0
- package/dist/client/components/Header.js.map +1 -0
- package/dist/client/components/Image.d.ts +20 -0
- package/dist/client/components/Image.d.ts.map +1 -0
- package/dist/client/components/Image.js +12 -0
- package/dist/client/components/Image.js.map +1 -0
- package/dist/client/components/Input.d.ts +14 -0
- package/dist/client/components/Input.d.ts.map +1 -0
- package/dist/client/components/Input.js +19 -0
- package/dist/client/components/Input.js.map +1 -0
- package/dist/client/components/Modal.d.ts +9 -0
- package/dist/client/components/Modal.d.ts.map +1 -0
- package/dist/client/components/Modal.js +19 -0
- package/dist/client/components/Modal.js.map +1 -0
- package/dist/client/components/PageLayout.d.ts +9 -0
- package/dist/client/components/PageLayout.d.ts.map +1 -0
- package/dist/client/components/PageLayout.js +6 -0
- package/dist/client/components/PageLayout.js.map +1 -0
- package/dist/client/components/Panel.d.ts +8 -0
- package/dist/client/components/Panel.d.ts.map +1 -0
- package/dist/client/components/Panel.js +5 -0
- package/dist/client/components/Panel.js.map +1 -0
- package/dist/client/components/PopupPanel.d.ts +9 -0
- package/dist/client/components/PopupPanel.d.ts.map +1 -0
- package/dist/client/components/PopupPanel.js +5 -0
- package/dist/client/components/PopupPanel.js.map +1 -0
- package/dist/client/components/Pressable.d.ts +14 -0
- package/dist/client/components/Pressable.d.ts.map +1 -0
- package/dist/client/components/Pressable.js +8 -0
- package/dist/client/components/Pressable.js.map +1 -0
- package/dist/client/components/ScrollView.d.ts +9 -0
- package/dist/client/components/ScrollView.d.ts.map +1 -0
- package/dist/client/components/ScrollView.js +7 -0
- package/dist/client/components/ScrollView.js.map +1 -0
- package/dist/client/components/SettingGroup.d.ts +10 -0
- package/dist/client/components/SettingGroup.d.ts.map +1 -0
- package/dist/client/components/SettingGroup.js +8 -0
- package/dist/client/components/SettingGroup.js.map +1 -0
- package/dist/client/components/Text.d.ts +21 -0
- package/dist/client/components/Text.d.ts.map +1 -0
- package/dist/client/components/Text.js +43 -0
- package/dist/client/components/Text.js.map +1 -0
- package/dist/client/components/Toast.d.ts +10 -0
- package/dist/client/components/Toast.d.ts.map +1 -0
- package/dist/client/components/Toast.js +21 -0
- package/dist/client/components/Toast.js.map +1 -0
- package/dist/client/components/View.d.ts +9 -0
- package/dist/client/components/View.d.ts.map +1 -0
- package/dist/client/components/View.js +5 -0
- package/dist/client/components/View.js.map +1 -0
- package/dist/client/components/index.d.ts +33 -0
- package/dist/client/components/index.d.ts.map +1 -0
- package/dist/client/components/index.js +17 -0
- package/dist/client/components/index.js.map +1 -0
- package/dist/client/components/zIndex.d.ts +10 -0
- package/dist/client/components/zIndex.d.ts.map +1 -0
- package/dist/client/components/zIndex.js +12 -0
- package/dist/client/components/zIndex.js.map +1 -0
- package/dist/client/createSocket.d.ts +26 -0
- package/dist/client/createSocket.d.ts.map +1 -0
- package/dist/client/createSocket.js +138 -0
- package/dist/client/createSocket.js.map +1 -0
- package/dist/client/index.d.ts +27 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +20 -0
- package/dist/client/index.js.map +1 -0
- package/dist/playwright/index.d.ts +16 -0
- package/dist/playwright/index.d.ts.map +1 -0
- package/dist/playwright/index.js +32 -0
- package/dist/playwright/index.js.map +1 -0
- package/dist/server/App.d.ts +43 -0
- package/dist/server/App.d.ts.map +1 -0
- package/dist/server/App.js +215 -0
- package/dist/server/App.js.map +1 -0
- package/dist/server/Auth.d.ts +21 -0
- package/dist/server/Auth.d.ts.map +1 -0
- package/dist/server/Auth.js +171 -0
- package/dist/server/Auth.js.map +1 -0
- package/dist/server/Cache.d.ts +38 -0
- package/dist/server/Cache.d.ts.map +1 -0
- package/dist/server/Cache.js +113 -0
- package/dist/server/Cache.js.map +1 -0
- package/dist/server/ChangeStream.d.ts +22 -0
- package/dist/server/ChangeStream.d.ts.map +1 -0
- package/dist/server/ChangeStream.js +93 -0
- package/dist/server/ChangeStream.js.map +1 -0
- package/dist/server/DB.d.ts +17 -0
- package/dist/server/DB.d.ts.map +1 -0
- package/dist/server/DB.js +442 -0
- package/dist/server/DB.js.map +1 -0
- package/dist/server/Email.d.ts +22 -0
- package/dist/server/Email.d.ts.map +1 -0
- package/dist/server/Email.js +64 -0
- package/dist/server/Email.js.map +1 -0
- package/dist/server/EmailTemplate.d.ts +17 -0
- package/dist/server/EmailTemplate.d.ts.map +1 -0
- package/dist/server/EmailTemplate.js +29 -0
- package/dist/server/EmailTemplate.js.map +1 -0
- package/dist/server/FeedbackReport.d.ts +19 -0
- package/dist/server/FeedbackReport.d.ts.map +1 -0
- package/dist/server/FeedbackReport.js +94 -0
- package/dist/server/FeedbackReport.js.map +1 -0
- package/dist/server/ImageGen.d.ts +5 -0
- package/dist/server/ImageGen.d.ts.map +1 -0
- package/dist/server/ImageGen.js +5 -0
- package/dist/server/ImageGen.js.map +1 -0
- package/dist/server/Logging.d.ts +59 -0
- package/dist/server/Logging.d.ts.map +1 -0
- package/dist/server/Logging.js +293 -0
- package/dist/server/Logging.js.map +1 -0
- package/dist/server/Nats.d.ts +56 -0
- package/dist/server/Nats.d.ts.map +1 -0
- package/dist/server/Nats.js +162 -0
- package/dist/server/Nats.js.map +1 -0
- package/dist/server/NatsStore.d.ts +26 -0
- package/dist/server/NatsStore.d.ts.map +1 -0
- package/dist/server/NatsStore.js +86 -0
- package/dist/server/NatsStore.js.map +1 -0
- package/dist/server/PushNotification.d.ts +33 -0
- package/dist/server/PushNotification.d.ts.map +1 -0
- package/dist/server/PushNotification.js +131 -0
- package/dist/server/PushNotification.js.map +1 -0
- package/dist/server/RateLimit.d.ts +28 -0
- package/dist/server/RateLimit.d.ts.map +1 -0
- package/dist/server/RateLimit.js +101 -0
- package/dist/server/RateLimit.js.map +1 -0
- package/dist/server/Redis.d.ts +61 -0
- package/dist/server/Redis.d.ts.map +1 -0
- package/dist/server/Redis.js +226 -0
- package/dist/server/Redis.js.map +1 -0
- package/dist/server/Router.d.ts +27 -0
- package/dist/server/Router.d.ts.map +1 -0
- package/dist/server/Router.js +71 -0
- package/dist/server/Router.js.map +1 -0
- package/dist/server/Socket.d.ts +8 -0
- package/dist/server/Socket.d.ts.map +1 -0
- package/dist/server/Socket.js +278 -0
- package/dist/server/Socket.js.map +1 -0
- package/dist/server/SpendTracker.d.ts +18 -0
- package/dist/server/SpendTracker.d.ts.map +1 -0
- package/dist/server/SpendTracker.js +110 -0
- package/dist/server/SpendTracker.js.map +1 -0
- package/dist/server/Storage.d.ts +16 -0
- package/dist/server/Storage.d.ts.map +1 -0
- package/dist/server/Storage.js +95 -0
- package/dist/server/Storage.js.map +1 -0
- package/dist/server/TextGen.d.ts +5 -0
- package/dist/server/TextGen.d.ts.map +1 -0
- package/dist/server/TextGen.js +6 -0
- package/dist/server/TextGen.js.map +1 -0
- package/dist/server/WorkerQueue.d.ts +27 -0
- package/dist/server/WorkerQueue.d.ts.map +1 -0
- package/dist/server/WorkerQueue.js +147 -0
- package/dist/server/WorkerQueue.js.map +1 -0
- package/dist/server/ai/ImageGenClient.d.ts +6 -0
- package/dist/server/ai/ImageGenClient.d.ts.map +1 -0
- package/dist/server/ai/ImageGenClient.js +29 -0
- package/dist/server/ai/ImageGenClient.js.map +1 -0
- package/dist/server/ai/ProviderBalance.d.ts +15 -0
- package/dist/server/ai/ProviderBalance.d.ts.map +1 -0
- package/dist/server/ai/ProviderBalance.js +110 -0
- package/dist/server/ai/ProviderBalance.js.map +1 -0
- package/dist/server/ai/ProviderSelector.d.ts +13 -0
- package/dist/server/ai/ProviderSelector.d.ts.map +1 -0
- package/dist/server/ai/ProviderSelector.js +25 -0
- package/dist/server/ai/ProviderSelector.js.map +1 -0
- package/dist/server/ai/TextGenClient.d.ts +9 -0
- package/dist/server/ai/TextGenClient.d.ts.map +1 -0
- package/dist/server/ai/TextGenClient.js +72 -0
- package/dist/server/ai/TextGenClient.js.map +1 -0
- package/dist/server/ai/fallbacks.d.ts +9 -0
- package/dist/server/ai/fallbacks.d.ts.map +1 -0
- package/dist/server/ai/fallbacks.js +77 -0
- package/dist/server/ai/fallbacks.js.map +1 -0
- package/dist/server/ai/index.d.ts +12 -0
- package/dist/server/ai/index.d.ts.map +1 -0
- package/dist/server/ai/index.js +37 -0
- package/dist/server/ai/index.js.map +1 -0
- package/dist/server/ai/providers/Claude.d.ts +3 -0
- package/dist/server/ai/providers/Claude.d.ts.map +1 -0
- package/dist/server/ai/providers/Claude.js +124 -0
- package/dist/server/ai/providers/Claude.js.map +1 -0
- package/dist/server/ai/providers/FAL.d.ts +3 -0
- package/dist/server/ai/providers/FAL.d.ts.map +1 -0
- package/dist/server/ai/providers/FAL.js +24 -0
- package/dist/server/ai/providers/FAL.js.map +1 -0
- package/dist/server/ai/providers/Fireworks.d.ts +3 -0
- package/dist/server/ai/providers/Fireworks.d.ts.map +1 -0
- package/dist/server/ai/providers/Fireworks.js +79 -0
- package/dist/server/ai/providers/Fireworks.js.map +1 -0
- package/dist/server/ai/providers/Google.d.ts +3 -0
- package/dist/server/ai/providers/Google.d.ts.map +1 -0
- package/dist/server/ai/providers/Google.js +105 -0
- package/dist/server/ai/providers/Google.js.map +1 -0
- package/dist/server/ai/providers/GoogleImage.d.ts +3 -0
- package/dist/server/ai/providers/GoogleImage.d.ts.map +1 -0
- package/dist/server/ai/providers/GoogleImage.js +24 -0
- package/dist/server/ai/providers/GoogleImage.js.map +1 -0
- package/dist/server/ai/providers/Groq.d.ts +3 -0
- package/dist/server/ai/providers/Groq.d.ts.map +1 -0
- package/dist/server/ai/providers/Groq.js +99 -0
- package/dist/server/ai/providers/Groq.js.map +1 -0
- package/dist/server/ai/providers/Kie.d.ts +3 -0
- package/dist/server/ai/providers/Kie.d.ts.map +1 -0
- package/dist/server/ai/providers/Kie.js +79 -0
- package/dist/server/ai/providers/Kie.js.map +1 -0
- package/dist/server/ai/providers/KieImage.d.ts +3 -0
- package/dist/server/ai/providers/KieImage.d.ts.map +1 -0
- package/dist/server/ai/providers/KieImage.js +50 -0
- package/dist/server/ai/providers/KieImage.js.map +1 -0
- package/dist/server/ai/providers/OpenAIText.d.ts +3 -0
- package/dist/server/ai/providers/OpenAIText.d.ts.map +1 -0
- package/dist/server/ai/providers/OpenAIText.js +103 -0
- package/dist/server/ai/providers/OpenAIText.js.map +1 -0
- package/dist/server/ai/providers/Together.d.ts +3 -0
- package/dist/server/ai/providers/Together.d.ts.map +1 -0
- package/dist/server/ai/providers/Together.js +99 -0
- package/dist/server/ai/providers/Together.js.map +1 -0
- package/dist/server/ai/providers/TogetherImage.d.ts +3 -0
- package/dist/server/ai/providers/TogetherImage.d.ts.map +1 -0
- package/dist/server/ai/providers/TogetherImage.js +24 -0
- package/dist/server/ai/providers/TogetherImage.js.map +1 -0
- package/dist/server/ai/providers/Wavespeed.d.ts +3 -0
- package/dist/server/ai/providers/Wavespeed.d.ts.map +1 -0
- package/dist/server/ai/providers/Wavespeed.js +53 -0
- package/dist/server/ai/providers/Wavespeed.js.map +1 -0
- package/dist/server/ai/registry.d.ts +10 -0
- package/dist/server/ai/registry.d.ts.map +1 -0
- package/dist/server/ai/registry.js +26 -0
- package/dist/server/ai/registry.js.map +1 -0
- package/dist/server/ai/types.d.ts +67 -0
- package/dist/server/ai/types.d.ts.map +1 -0
- package/dist/server/ai/types.js +17 -0
- package/dist/server/ai/types.js.map +1 -0
- package/dist/server/audio/STTStream.d.ts +12 -0
- package/dist/server/audio/STTStream.d.ts.map +1 -0
- package/dist/server/audio/STTStream.js +59 -0
- package/dist/server/audio/STTStream.js.map +1 -0
- package/dist/server/audio/TTSStream.d.ts +16 -0
- package/dist/server/audio/TTSStream.d.ts.map +1 -0
- package/dist/server/audio/TTSStream.js +151 -0
- package/dist/server/audio/TTSStream.js.map +1 -0
- package/dist/server/audio/index.d.ts +5 -0
- package/dist/server/audio/index.d.ts.map +1 -0
- package/dist/server/audio/index.js +14 -0
- package/dist/server/audio/index.js.map +1 -0
- package/dist/server/audio/resample.d.ts +7 -0
- package/dist/server/audio/resample.d.ts.map +1 -0
- package/dist/server/audio/resample.js +17 -0
- package/dist/server/audio/resample.js.map +1 -0
- package/dist/server/audio/stt/GroqWhisper.d.ts +3 -0
- package/dist/server/audio/stt/GroqWhisper.d.ts.map +1 -0
- package/dist/server/audio/stt/GroqWhisper.js +91 -0
- package/dist/server/audio/stt/GroqWhisper.js.map +1 -0
- package/dist/server/audio/stt/registry.d.ts +5 -0
- package/dist/server/audio/stt/registry.d.ts.map +1 -0
- package/dist/server/audio/stt/registry.js +11 -0
- package/dist/server/audio/stt/registry.js.map +1 -0
- package/dist/server/audio/stt/types.d.ts +25 -0
- package/dist/server/audio/stt/types.d.ts.map +1 -0
- package/dist/server/audio/stt/types.js +2 -0
- package/dist/server/audio/stt/types.js.map +1 -0
- package/dist/server/audio/stt/vad.d.ts +18 -0
- package/dist/server/audio/stt/vad.d.ts.map +1 -0
- package/dist/server/audio/stt/vad.js +90 -0
- package/dist/server/audio/stt/vad.js.map +1 -0
- package/dist/server/audio/tts/InWorld.d.ts +3 -0
- package/dist/server/audio/tts/InWorld.d.ts.map +1 -0
- package/dist/server/audio/tts/InWorld.js +147 -0
- package/dist/server/audio/tts/InWorld.js.map +1 -0
- package/dist/server/audio/tts/providers/Azure.d.ts +3 -0
- package/dist/server/audio/tts/providers/Azure.d.ts.map +1 -0
- package/dist/server/audio/tts/providers/Azure.js +31 -0
- package/dist/server/audio/tts/providers/Azure.js.map +1 -0
- package/dist/server/audio/tts/registry.d.ts +6 -0
- package/dist/server/audio/tts/registry.d.ts.map +1 -0
- package/dist/server/audio/tts/registry.js +12 -0
- package/dist/server/audio/tts/registry.js.map +1 -0
- package/dist/server/audio/tts/types.d.ts +20 -0
- package/dist/server/audio/tts/types.d.ts.map +1 -0
- package/dist/server/audio/tts/types.js +2 -0
- package/dist/server/audio/tts/types.js.map +1 -0
- package/dist/server/billing/BillingGateway.d.ts +40 -0
- package/dist/server/billing/BillingGateway.d.ts.map +1 -0
- package/dist/server/billing/BillingGateway.js +116 -0
- package/dist/server/billing/BillingGateway.js.map +1 -0
- package/dist/server/billing/BillingLedger.d.ts +67 -0
- package/dist/server/billing/BillingLedger.d.ts.map +1 -0
- package/dist/server/billing/BillingLedger.js +214 -0
- package/dist/server/billing/BillingLedger.js.map +1 -0
- package/dist/server/billing/CreditStore.d.ts +58 -0
- package/dist/server/billing/CreditStore.d.ts.map +1 -0
- package/dist/server/billing/CreditStore.js +112 -0
- package/dist/server/billing/CreditStore.js.map +1 -0
- package/dist/server/billing/LimitEnforcer.d.ts +73 -0
- package/dist/server/billing/LimitEnforcer.d.ts.map +1 -0
- package/dist/server/billing/LimitEnforcer.js +276 -0
- package/dist/server/billing/LimitEnforcer.js.map +1 -0
- package/dist/server/billing/UserLimitCache.d.ts +35 -0
- package/dist/server/billing/UserLimitCache.d.ts.map +1 -0
- package/dist/server/billing/UserLimitCache.js +91 -0
- package/dist/server/billing/UserLimitCache.js.map +1 -0
- package/dist/server/billing/index.d.ts +6 -0
- package/dist/server/billing/index.d.ts.map +1 -0
- package/dist/server/billing/index.js +4 -0
- package/dist/server/billing/index.js.map +1 -0
- package/dist/server/billing/types.d.ts +95 -0
- package/dist/server/billing/types.d.ts.map +1 -0
- package/dist/server/billing/types.js +19 -0
- package/dist/server/billing/types.js.map +1 -0
- package/dist/server/embeddings/EmbeddingClient.d.ts +8 -0
- package/dist/server/embeddings/EmbeddingClient.d.ts.map +1 -0
- package/dist/server/embeddings/EmbeddingClient.js +30 -0
- package/dist/server/embeddings/EmbeddingClient.js.map +1 -0
- package/dist/server/embeddings/index.d.ts +5 -0
- package/dist/server/embeddings/index.d.ts.map +1 -0
- package/dist/server/embeddings/index.js +6 -0
- package/dist/server/embeddings/index.js.map +1 -0
- package/dist/server/embeddings/providers/OpenAI.d.ts +3 -0
- package/dist/server/embeddings/providers/OpenAI.d.ts.map +1 -0
- package/dist/server/embeddings/providers/OpenAI.js +28 -0
- package/dist/server/embeddings/providers/OpenAI.js.map +1 -0
- package/dist/server/embeddings/registry.d.ts +7 -0
- package/dist/server/embeddings/registry.d.ts.map +1 -0
- package/dist/server/embeddings/registry.js +15 -0
- package/dist/server/embeddings/registry.js.map +1 -0
- package/dist/server/embeddings/types.d.ts +12 -0
- package/dist/server/embeddings/types.d.ts.map +1 -0
- package/dist/server/embeddings/types.js +2 -0
- package/dist/server/embeddings/types.js.map +1 -0
- package/dist/server/index.d.ts +59 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +41 -0
- package/dist/server/index.js.map +1 -0
- package/dist/shared/Api.d.ts +40 -0
- package/dist/shared/Api.d.ts.map +1 -0
- package/dist/shared/Api.js +30 -0
- package/dist/shared/Api.js.map +1 -0
- package/dist/shared/Audio.d.ts +108 -0
- package/dist/shared/Audio.d.ts.map +1 -0
- package/dist/shared/Audio.js +3 -0
- package/dist/shared/Audio.js.map +1 -0
- package/dist/shared/DB.d.ts +339 -0
- package/dist/shared/DB.d.ts.map +1 -0
- package/dist/shared/DB.js +51 -0
- package/dist/shared/DB.js.map +1 -0
- package/dist/shared/Errors.d.ts +10 -0
- package/dist/shared/Errors.d.ts.map +1 -0
- package/dist/shared/Errors.js +19 -0
- package/dist/shared/Errors.js.map +1 -0
- package/dist/shared/FeedbackReport.d.ts +38 -0
- package/dist/shared/FeedbackReport.d.ts.map +1 -0
- package/dist/shared/FeedbackReport.js +14 -0
- package/dist/shared/FeedbackReport.js.map +1 -0
- package/dist/shared/Router.d.ts +28 -0
- package/dist/shared/Router.d.ts.map +1 -0
- package/dist/shared/Router.js +82 -0
- package/dist/shared/Router.js.map +1 -0
- package/dist/shared/Socket.d.ts +26 -0
- package/dist/shared/Socket.d.ts.map +1 -0
- package/dist/shared/Socket.js +2 -0
- package/dist/shared/Socket.js.map +1 -0
- package/dist/shared/index.d.ts +28 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +61 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/webrtc/index.d.ts +14 -0
- package/dist/webrtc/index.d.ts.map +1 -0
- package/dist/webrtc/index.js +24 -0
- package/dist/webrtc/index.js.map +1 -0
- package/package.json +97 -0
- package/templates/.claude/skills/assets.md +39 -0
- package/templates/.claude/skills/check-errors.md +35 -0
- package/templates/.claude/skills/check-feedback.md +23 -0
- package/templates/.claude/skills/check-perf.md +22 -0
- package/templates/.claude/skills/create-test-users.md +34 -0
- package/templates/.claude/skills/extend-api.md +37 -0
- package/templates/.claude/skills/fix-code.md +5 -0
- package/templates/.claude/skills/fix-errors.md +9 -0
- package/templates/.claude/skills/fix-feedback.md +8 -0
- package/templates/.claude/skills/fix-perf.md +8 -0
- package/templates/.claude/skills/uploads.md +38 -0
- package/templates/.claude/skills/use-ai.md +49 -0
- package/templates/.env.example +49 -0
- package/templates/.husky/pre-commit +2 -0
- package/templates/CLAUDE.md +199 -0
- package/templates/client/App.tsx +33 -0
- package/templates/client/index.html +14 -0
- package/templates/client/main.tsx +52 -0
- package/templates/client/pages/AuthDemoPage.tsx +85 -0
- package/templates/client/pages/HomePage.tsx +111 -0
- package/templates/client/pages/SearchPage.tsx +48 -0
- package/templates/client/pages/UserPage.tsx +33 -0
- package/templates/client/router.ts +4 -0
- package/templates/docker-compose.yml +50 -0
- package/templates/index.html +14 -0
- package/templates/package.json +56 -0
- package/templates/playwright.config.ts +21 -0
- package/templates/server/index.ts +24 -0
- package/templates/shared/api.ts +36 -0
- package/templates/shared/collections.ts +11 -0
- package/templates/shared/dbIndexes.ts +11 -0
- package/templates/shared/pages.ts +8 -0
- package/templates/vite.config.ts +17 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { connect, RetentionPolicy, StringCodec, } from 'nats';
|
|
2
|
+
const codec = StringCodec();
|
|
3
|
+
let natsConnection = null;
|
|
4
|
+
let jetStreamClient = null;
|
|
5
|
+
// Active subscriptions tracked for leak detection
|
|
6
|
+
const activeSubscriptions = new Map();
|
|
7
|
+
let subIdCounter = 0;
|
|
8
|
+
const SUBSCRIPTION_LEAK_THRESHOLD = 20;
|
|
9
|
+
/**
|
|
10
|
+
* Connect to NATS. Called once at server startup.
|
|
11
|
+
* Development: connects to localhost:4222
|
|
12
|
+
* Production: uses NATS_URL env var (and NATS_CREDS for Synadia Cloud)
|
|
13
|
+
*/
|
|
14
|
+
export async function connectNats(config) {
|
|
15
|
+
const url = config?.url ?? process.env['NATS_URL'] ?? 'nats://localhost:4222';
|
|
16
|
+
const connectOptions = { servers: url };
|
|
17
|
+
if (config?.creds ?? process.env['NATS_CREDS']) {
|
|
18
|
+
const { credsAuthenticator } = await import('nats');
|
|
19
|
+
const fs = await import('fs');
|
|
20
|
+
const credsPath = config?.creds ?? process.env['NATS_CREDS'];
|
|
21
|
+
const credsContent = fs.readFileSync(credsPath);
|
|
22
|
+
connectOptions.authenticator = credsAuthenticator(credsContent);
|
|
23
|
+
}
|
|
24
|
+
natsConnection = await connect(connectOptions);
|
|
25
|
+
console.log('[NATS] Connected', { url });
|
|
26
|
+
natsConnection
|
|
27
|
+
.closed()
|
|
28
|
+
.then(() => {
|
|
29
|
+
console.log('[NATS] Connection closed');
|
|
30
|
+
natsConnection = null;
|
|
31
|
+
jetStreamClient = null;
|
|
32
|
+
})
|
|
33
|
+
.catch((err) => {
|
|
34
|
+
console.error('[NATS] Connection closed with error', err);
|
|
35
|
+
natsConnection = null;
|
|
36
|
+
jetStreamClient = null;
|
|
37
|
+
});
|
|
38
|
+
return natsConnection;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the active NATS connection. Throws if not connected.
|
|
42
|
+
*/
|
|
43
|
+
export function getNatsConnection() {
|
|
44
|
+
if (!natsConnection) {
|
|
45
|
+
throw new Error('[NATS] Not connected. Call connectNats() first.');
|
|
46
|
+
}
|
|
47
|
+
return natsConnection;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Publish a message to a NATS subject.
|
|
51
|
+
* @param subject - NATS subject (e.g. 'user.updates.abc123')
|
|
52
|
+
* @param payload - JSON-serializable payload
|
|
53
|
+
*/
|
|
54
|
+
export function natsPublish(subject, payload) {
|
|
55
|
+
const conn = getNatsConnection();
|
|
56
|
+
conn.publish(subject, codec.encode(JSON.stringify(payload)));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Subscribe to a NATS subject.
|
|
60
|
+
* Returns an unsubscribe function — ALWAYS call it when done to prevent leaks.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const unsub = await natsSubscribe('user.updates.*', (msg) => {
|
|
64
|
+
* console.log(msg.subject, msg.data);
|
|
65
|
+
* });
|
|
66
|
+
* // Later:
|
|
67
|
+
* unsub();
|
|
68
|
+
*/
|
|
69
|
+
export async function natsSubscribe(subject, handler) {
|
|
70
|
+
const conn = getNatsConnection();
|
|
71
|
+
const sub = conn.subscribe(subject);
|
|
72
|
+
const subId = ++subIdCounter;
|
|
73
|
+
activeSubscriptions.set(String(subId), { subject, createdAt: new Date() });
|
|
74
|
+
// Check for subscription leaks
|
|
75
|
+
if (activeSubscriptions.size > SUBSCRIPTION_LEAK_THRESHOLD) {
|
|
76
|
+
const oldest = [...activeSubscriptions.entries()].sort((a, b) => a[1].createdAt.getTime() - b[1].createdAt.getTime())[0];
|
|
77
|
+
console.error('[NATS] Possible subscription leak detected', {
|
|
78
|
+
activeCount: activeSubscriptions.size,
|
|
79
|
+
threshold: SUBSCRIPTION_LEAK_THRESHOLD,
|
|
80
|
+
oldestSubject: oldest?.[1]?.subject,
|
|
81
|
+
oldestAge: oldest
|
|
82
|
+
? `${Math.round((Date.now() - oldest[1].createdAt.getTime()) / 1000)}s`
|
|
83
|
+
: 'unknown',
|
|
84
|
+
fix: 'Ensure you call the unsubscribe() function returned by natsSubscribe()',
|
|
85
|
+
example: 'const unsub = await natsSubscribe(subject, handler); // later: unsub();',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Process messages asynchronously
|
|
89
|
+
(async () => {
|
|
90
|
+
for await (const msg of sub) {
|
|
91
|
+
try {
|
|
92
|
+
const data = JSON.parse(codec.decode(msg.data));
|
|
93
|
+
await handler({ subject: msg.subject, data });
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error('[NATS] Message handler error', {
|
|
97
|
+
subject: msg.subject,
|
|
98
|
+
err,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
})().catch((err) => {
|
|
103
|
+
console.error('[NATS] Subscription loop error', { subject, err });
|
|
104
|
+
});
|
|
105
|
+
return () => {
|
|
106
|
+
sub.unsubscribe();
|
|
107
|
+
activeSubscriptions.delete(String(subId));
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the JetStream client for durable queues.
|
|
112
|
+
* JetStream must be enabled on the NATS server (pass -js flag).
|
|
113
|
+
*/
|
|
114
|
+
export async function getJetStream() {
|
|
115
|
+
if (!jetStreamClient) {
|
|
116
|
+
const conn = getNatsConnection();
|
|
117
|
+
jetStreamClient = conn.jetstream();
|
|
118
|
+
}
|
|
119
|
+
return jetStreamClient;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Ensure a JetStream stream exists (idempotent).
|
|
123
|
+
* Creates the stream if it doesn't exist; updates if config differs.
|
|
124
|
+
*/
|
|
125
|
+
export async function ensureStream(config) {
|
|
126
|
+
const conn = getNatsConnection();
|
|
127
|
+
const jsm = await conn.jetstreamManager();
|
|
128
|
+
try {
|
|
129
|
+
await jsm.streams.info(config.name);
|
|
130
|
+
// Stream exists, update subjects (retention policy cannot be changed after creation)
|
|
131
|
+
await jsm.streams.update(config.name, {
|
|
132
|
+
subjects: config.subjects,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
// Only create if stream doesn't exist; re-throw other errors
|
|
137
|
+
const isNotFound = err instanceof Error &&
|
|
138
|
+
(err.message.includes('stream not found') || err.message.includes('404'));
|
|
139
|
+
if (!isNotFound) {
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
// Stream doesn't exist, create it
|
|
143
|
+
await jsm.streams.add({
|
|
144
|
+
name: config.name,
|
|
145
|
+
subjects: config.subjects,
|
|
146
|
+
retention: config.retentionPolicy ?? RetentionPolicy.Limits,
|
|
147
|
+
});
|
|
148
|
+
console.log('[NATS] Stream created', { name: config.name });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Gracefully close the NATS connection.
|
|
153
|
+
* Call during server shutdown.
|
|
154
|
+
*/
|
|
155
|
+
export async function closeNats() {
|
|
156
|
+
if (natsConnection) {
|
|
157
|
+
await natsConnection.close();
|
|
158
|
+
natsConnection = null;
|
|
159
|
+
jetStreamClient = null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=Nats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Nats.js","sourceRoot":"","sources":["../../src/server/Nats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DBObject } from '../shared/DB.js';
|
|
2
|
+
export type NatsDocMessage<T = DBObject> = {
|
|
3
|
+
type: 'update';
|
|
4
|
+
item: T;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'delete';
|
|
7
|
+
id: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to changes for a specific document.
|
|
11
|
+
* Subject format: db.{collection}.{escapeNatsToken(id)}
|
|
12
|
+
* Returns an unsubscribe function (synchronous).
|
|
13
|
+
*
|
|
14
|
+
* Do NOT use natsSubscribe() from Nats.ts — it is async. Call conn.subscribe() directly.
|
|
15
|
+
*/
|
|
16
|
+
export declare function subscribeDoc(collection: string, id: string, callback: (msg: NatsDocMessage) => void): () => void;
|
|
17
|
+
/**
|
|
18
|
+
* Subscribe to all doc-level changes in a collection.
|
|
19
|
+
* Subject: db.{collection}.> (wildcard — matches all doc-level subjects).
|
|
20
|
+
* The bare db.{collection} collection-level events are intentionally not subscribed to;
|
|
21
|
+
* DocumentNotifier always publishes both, so doc-level events alone are sufficient.
|
|
22
|
+
* Returns an unsubscribe function (synchronous).
|
|
23
|
+
*/
|
|
24
|
+
export declare function subscribeCollection(collection: string, callback: (msg: NatsDocMessage) => void): () => void;
|
|
25
|
+
export declare function escapeNatsToken(token: string): string;
|
|
26
|
+
//# sourceMappingURL=NatsStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NatsStore.d.ts","sourceRoot":"","sources":["../../src/server/NatsStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAsBhD,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,QAAQ,IACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GACtC,MAAM,IAAI,CAuBZ;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GACtC,MAAM,IAAI,CAuBZ;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMrD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { StringCodec } from 'nats';
|
|
2
|
+
import { getNatsConnection } from './Nats.js';
|
|
3
|
+
const sc = StringCodec();
|
|
4
|
+
// Subscription leak detection — separate from Nats.ts's tracker because
|
|
5
|
+
// NatsStore calls conn.subscribe() directly (not natsSubscribe()).
|
|
6
|
+
// Threshold is 100 (vs Nats.ts's 20) because each tracked doc per connected
|
|
7
|
+
// socket opens one subscription; steady-state with active users easily reaches 50+.
|
|
8
|
+
let activeCount = 0;
|
|
9
|
+
const LEAK_THRESHOLD = 100;
|
|
10
|
+
function checkLeak(subject) {
|
|
11
|
+
activeCount++;
|
|
12
|
+
if (activeCount > LEAK_THRESHOLD) {
|
|
13
|
+
console.warn('[NatsStore] Subscription leak detected', {
|
|
14
|
+
activeCount,
|
|
15
|
+
subject,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to changes for a specific document.
|
|
21
|
+
* Subject format: db.{collection}.{escapeNatsToken(id)}
|
|
22
|
+
* Returns an unsubscribe function (synchronous).
|
|
23
|
+
*
|
|
24
|
+
* Do NOT use natsSubscribe() from Nats.ts — it is async. Call conn.subscribe() directly.
|
|
25
|
+
*/
|
|
26
|
+
export function subscribeDoc(collection, id, callback) {
|
|
27
|
+
const subject = `db.${collection}.${escapeNatsToken(id)}`;
|
|
28
|
+
const conn = getNatsConnection();
|
|
29
|
+
const sub = conn.subscribe(subject);
|
|
30
|
+
checkLeak(subject);
|
|
31
|
+
(async () => {
|
|
32
|
+
for await (const msg of sub) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(sc.decode(msg.data));
|
|
35
|
+
callback(parsed);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
console.error('[NatsStore] Failed to parse message', { subject, err });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
})().catch((err) => {
|
|
42
|
+
console.error('[NatsStore] Subscription loop error', { subject, err });
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
sub.unsubscribe();
|
|
46
|
+
activeCount--;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to all doc-level changes in a collection.
|
|
51
|
+
* Subject: db.{collection}.> (wildcard — matches all doc-level subjects).
|
|
52
|
+
* The bare db.{collection} collection-level events are intentionally not subscribed to;
|
|
53
|
+
* DocumentNotifier always publishes both, so doc-level events alone are sufficient.
|
|
54
|
+
* Returns an unsubscribe function (synchronous).
|
|
55
|
+
*/
|
|
56
|
+
export function subscribeCollection(collection, callback) {
|
|
57
|
+
const subject = `db.${collection}.>`;
|
|
58
|
+
const conn = getNatsConnection();
|
|
59
|
+
const sub = conn.subscribe(subject);
|
|
60
|
+
checkLeak(subject);
|
|
61
|
+
(async () => {
|
|
62
|
+
for await (const msg of sub) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(sc.decode(msg.data));
|
|
65
|
+
callback(parsed);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error('[NatsStore] Failed to parse message', { subject, err });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})().catch((err) => {
|
|
72
|
+
console.error('[NatsStore] Subscription loop error', { subject, err });
|
|
73
|
+
});
|
|
74
|
+
return () => {
|
|
75
|
+
sub.unsubscribe();
|
|
76
|
+
activeCount--;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function escapeNatsToken(token) {
|
|
80
|
+
return token
|
|
81
|
+
.replace(/\./g, '_DOT_')
|
|
82
|
+
.replace(/\+/g, '_PLUS_')
|
|
83
|
+
.replace(/\*/g, '_STAR_')
|
|
84
|
+
.replace(/>/g, '_GT_');
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=NatsStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NatsStore.js","sourceRoot":"","sources":["../../src/server/NatsStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WebSocket } from 'ws';
|
|
2
|
+
export interface PushNotification {
|
|
3
|
+
title: string;
|
|
4
|
+
body: string;
|
|
5
|
+
uri?: string;
|
|
6
|
+
imageUrl?: string;
|
|
7
|
+
data?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface PushToken {
|
|
10
|
+
userId: string;
|
|
11
|
+
token: string;
|
|
12
|
+
platform: 'web' | 'ios' | 'android';
|
|
13
|
+
updatedAt: Date;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Register a WebSocket connection for push notifications.
|
|
17
|
+
* Called when a user authenticates a WebSocket session.
|
|
18
|
+
* Returns an unregister function to call when connection closes.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerPushConnection(userId: string, ws: WebSocket): Promise<() => void>;
|
|
21
|
+
/**
|
|
22
|
+
* Send a push notification to a user.
|
|
23
|
+
* Delivers via WebSocket (cross-server via Redis pub/sub).
|
|
24
|
+
* Also sends via FCM if token is available and FCM is configured.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sendPush(userId: string, notification: PushNotification): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Send an FCM push notification.
|
|
29
|
+
* Only works if Firebase is configured via env vars:
|
|
30
|
+
* FIREBASE_PROJECT_ID, FIREBASE_PRIVATE_KEY, FIREBASE_CLIENT_EMAIL
|
|
31
|
+
*/
|
|
32
|
+
export declare function sendFcmPush(fcmToken: string, notification: PushNotification): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=PushNotification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PushNotification.d.ts","sourceRoot":"","sources":["../../src/server/PushNotification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAGpC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,IAAI,CAAC;CACjB;AAUD;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,SAAS,GACZ,OAAO,CAAC,MAAM,IAAI,CAAC,CAkCrB;AA4BD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,gBAAgB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,gBAAgB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAoDf"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { redisPublish, redisSubscribe } from './Redis.js';
|
|
2
|
+
const PUSH_CHANNEL_PREFIX = 'push:';
|
|
3
|
+
// Map of userId → Set of local WebSocket connections
|
|
4
|
+
const localConnections = new Map();
|
|
5
|
+
// Map of userId → Redis unsubscribe function (one subscription per userId)
|
|
6
|
+
const redisUnsubscribers = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Register a WebSocket connection for push notifications.
|
|
9
|
+
* Called when a user authenticates a WebSocket session.
|
|
10
|
+
* Returns an unregister function to call when connection closes.
|
|
11
|
+
*/
|
|
12
|
+
export async function registerPushConnection(userId, ws) {
|
|
13
|
+
if (!localConnections.has(userId)) {
|
|
14
|
+
localConnections.set(userId, new Set());
|
|
15
|
+
}
|
|
16
|
+
localConnections.get(userId).add(ws);
|
|
17
|
+
// Subscribe to Redis if this is the first connection for this user.
|
|
18
|
+
// Set a no-op placeholder BEFORE the await so concurrent calls for the same
|
|
19
|
+
// userId skip this branch and don't create duplicate Redis subscriptions.
|
|
20
|
+
if (!redisUnsubscribers.has(userId)) {
|
|
21
|
+
redisUnsubscribers.set(userId, () => { }); // placeholder — prevents race
|
|
22
|
+
const unsub = await redisSubscribe(`${PUSH_CHANNEL_PREFIX}${userId}`, (message) => {
|
|
23
|
+
deliverToLocalConnections(userId, message);
|
|
24
|
+
});
|
|
25
|
+
redisUnsubscribers.set(userId, unsub); // replace placeholder with real unsub
|
|
26
|
+
}
|
|
27
|
+
return () => {
|
|
28
|
+
const connections = localConnections.get(userId);
|
|
29
|
+
if (connections) {
|
|
30
|
+
connections.delete(ws);
|
|
31
|
+
if (connections.size === 0) {
|
|
32
|
+
localConnections.delete(userId);
|
|
33
|
+
const unsub = redisUnsubscribers.get(userId);
|
|
34
|
+
if (unsub) {
|
|
35
|
+
unsub();
|
|
36
|
+
redisUnsubscribers.delete(userId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function deliverToLocalConnections(userId, message) {
|
|
43
|
+
const connections = localConnections.get(userId);
|
|
44
|
+
if (!connections)
|
|
45
|
+
return;
|
|
46
|
+
let notification;
|
|
47
|
+
try {
|
|
48
|
+
notification = JSON.parse(message);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error('[Push] Failed to parse notification message', {
|
|
52
|
+
userId,
|
|
53
|
+
err,
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
for (const ws of connections) {
|
|
58
|
+
if (ws.readyState === 1 /* OPEN */) {
|
|
59
|
+
try {
|
|
60
|
+
ws.send(JSON.stringify({ type: 'pushNotification', data: notification }));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.error('[Push] WebSocket send error', { userId, err });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Send a push notification to a user.
|
|
70
|
+
* Delivers via WebSocket (cross-server via Redis pub/sub).
|
|
71
|
+
* Also sends via FCM if token is available and FCM is configured.
|
|
72
|
+
*/
|
|
73
|
+
export async function sendPush(userId, notification) {
|
|
74
|
+
const message = JSON.stringify(notification);
|
|
75
|
+
// Publish to Redis → all servers with connections for this user receive it
|
|
76
|
+
await redisPublish(`${PUSH_CHANNEL_PREFIX}${userId}`, message);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Send an FCM push notification.
|
|
80
|
+
* Only works if Firebase is configured via env vars:
|
|
81
|
+
* FIREBASE_PROJECT_ID, FIREBASE_PRIVATE_KEY, FIREBASE_CLIENT_EMAIL
|
|
82
|
+
*/
|
|
83
|
+
export async function sendFcmPush(fcmToken, notification) {
|
|
84
|
+
const projectId = process.env['FIREBASE_PROJECT_ID'];
|
|
85
|
+
const privateKey = process.env['FIREBASE_PRIVATE_KEY'];
|
|
86
|
+
const clientEmail = process.env['FIREBASE_CLIENT_EMAIL'];
|
|
87
|
+
if (!projectId || !privateKey || !clientEmail) {
|
|
88
|
+
console.error('[Push] FCM not configured - missing FIREBASE_* env vars');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
// Dynamic import via Function to avoid TS static resolution of optional dep
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
94
|
+
const importFn = new Function('m', 'return import(m)');
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
const admin = (await importFn('firebase-admin'));
|
|
97
|
+
if (!admin.apps.length) {
|
|
98
|
+
admin.initializeApp({
|
|
99
|
+
credential: admin.credential.cert({
|
|
100
|
+
projectId,
|
|
101
|
+
privateKey: privateKey.replace(/\\n/g, '\n'),
|
|
102
|
+
clientEmail,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
await admin.messaging().send({
|
|
107
|
+
token: fcmToken,
|
|
108
|
+
notification: {
|
|
109
|
+
title: notification.title,
|
|
110
|
+
body: notification.body,
|
|
111
|
+
imageUrl: notification.imageUrl,
|
|
112
|
+
},
|
|
113
|
+
webpush: notification.uri
|
|
114
|
+
? {
|
|
115
|
+
fcmOptions: { link: notification.uri },
|
|
116
|
+
}
|
|
117
|
+
: undefined,
|
|
118
|
+
apns: notification.uri
|
|
119
|
+
? {
|
|
120
|
+
payload: { aps: { sound: 'default' } },
|
|
121
|
+
fcmOptions: { imageUrl: notification.imageUrl },
|
|
122
|
+
}
|
|
123
|
+
: undefined,
|
|
124
|
+
data: notification.data,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error('[Push] FCM send error', { err });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=PushNotification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PushNotification.js","sourceRoot":"","sources":["../../src/server/PushNotification.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface RateLimitConfig {
|
|
2
|
+
windowSeconds?: number;
|
|
3
|
+
maxPerWindow?: number;
|
|
4
|
+
maxQueueDepth?: number;
|
|
5
|
+
maxWaitMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface UsageRecord {
|
|
8
|
+
userId: string;
|
|
9
|
+
operation: string;
|
|
10
|
+
used: number;
|
|
11
|
+
limit: number;
|
|
12
|
+
windowStart: Date;
|
|
13
|
+
queued: number;
|
|
14
|
+
}
|
|
15
|
+
export interface RateLimiter {
|
|
16
|
+
check(userId: string, operation: string, estimatedCost: number): Promise<void>;
|
|
17
|
+
charge(userId: string, operation: string, actualCost: number): Promise<void>;
|
|
18
|
+
getUsage(userId: string, operation: string): Promise<UsageRecord>;
|
|
19
|
+
}
|
|
20
|
+
export declare class RateLimitError extends Error {
|
|
21
|
+
readonly userId: string;
|
|
22
|
+
readonly operation: string;
|
|
23
|
+
readonly used: number;
|
|
24
|
+
readonly limit: number;
|
|
25
|
+
constructor(userId: string, operation: string, used: number, limit: number);
|
|
26
|
+
}
|
|
27
|
+
export declare function createRateLimiter(config?: RateLimitConfig): RateLimiter;
|
|
28
|
+
//# sourceMappingURL=RateLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimit.d.ts","sourceRoot":"","sources":["../../src/server/RateLimit.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAE9B,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAK1B,KAAK,CACH,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAAC;IAGjB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG7E,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACnE;AAED,qBAAa,cAAe,SAAQ,KAAK;aAErB,MAAM,EAAE,MAAM;aACd,SAAS,EAAE,MAAM;aACjB,IAAI,EAAE,MAAM;aACZ,KAAK,EAAE,MAAM;gBAHb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM;CAKhC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,eAAoB,GAAG,WAAW,CAgH3E"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export class RateLimitError extends Error {
|
|
2
|
+
userId;
|
|
3
|
+
operation;
|
|
4
|
+
used;
|
|
5
|
+
limit;
|
|
6
|
+
constructor(userId, operation, used, limit) {
|
|
7
|
+
super(`[RateLimit] Rate limit exceeded for operation '${operation}'`);
|
|
8
|
+
this.userId = userId;
|
|
9
|
+
this.operation = operation;
|
|
10
|
+
this.used = used;
|
|
11
|
+
this.limit = limit;
|
|
12
|
+
this.name = 'RateLimitError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function createRateLimiter(config = {}) {
|
|
16
|
+
const { windowSeconds = 3600, maxPerWindow = 1.0, maxQueueDepth = 100, maxWaitMs = 300_000, } = config;
|
|
17
|
+
const usages = new Map();
|
|
18
|
+
function getKey(userId, operation) {
|
|
19
|
+
return `${userId}:${operation}`;
|
|
20
|
+
}
|
|
21
|
+
function getUsageEntry(key) {
|
|
22
|
+
let entry = usages.get(key);
|
|
23
|
+
if (!entry) {
|
|
24
|
+
entry = { used: 0, windowStart: new Date(), queue: [] };
|
|
25
|
+
usages.set(key, entry);
|
|
26
|
+
}
|
|
27
|
+
// Reset window if expired
|
|
28
|
+
const age = (Date.now() - entry.windowStart.getTime()) / 1000;
|
|
29
|
+
if (age >= windowSeconds) {
|
|
30
|
+
entry.used = 0;
|
|
31
|
+
entry.windowStart = new Date();
|
|
32
|
+
}
|
|
33
|
+
return entry;
|
|
34
|
+
}
|
|
35
|
+
function drainQueue(entry) {
|
|
36
|
+
while (entry.queue.length > 0) {
|
|
37
|
+
const next = entry.queue[0];
|
|
38
|
+
if (entry.used + next.cost <= maxPerWindow) {
|
|
39
|
+
entry.queue.shift();
|
|
40
|
+
next.resolve();
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
async check(userId, operation, estimatedCost) {
|
|
49
|
+
const key = getKey(userId, operation);
|
|
50
|
+
const entry = getUsageEntry(key);
|
|
51
|
+
if (entry.used + estimatedCost <= maxPerWindow) {
|
|
52
|
+
// Within limit — proceed immediately
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// A single request that exceeds the entire window limit can never be served.
|
|
56
|
+
// Queuing it would block for maxWaitMs with no possibility of resolution.
|
|
57
|
+
if (estimatedCost > maxPerWindow) {
|
|
58
|
+
throw new RateLimitError(userId, operation, entry.used, maxPerWindow);
|
|
59
|
+
}
|
|
60
|
+
// Over limit — queue if there's room
|
|
61
|
+
if (entry.queue.length >= maxQueueDepth) {
|
|
62
|
+
throw new RateLimitError(userId, operation, entry.used, maxPerWindow);
|
|
63
|
+
}
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
const idx = entry.queue.findIndex((q) => q.resolve === resolve);
|
|
67
|
+
if (idx >= 0)
|
|
68
|
+
entry.queue.splice(idx, 1);
|
|
69
|
+
reject(new RateLimitError(userId, operation, entry.used, maxPerWindow));
|
|
70
|
+
}, maxWaitMs);
|
|
71
|
+
entry.queue.push({
|
|
72
|
+
cost: estimatedCost,
|
|
73
|
+
resolve: () => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
resolve();
|
|
76
|
+
},
|
|
77
|
+
reject,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
async charge(userId, operation, actualCost) {
|
|
82
|
+
const key = getKey(userId, operation);
|
|
83
|
+
const entry = getUsageEntry(key);
|
|
84
|
+
entry.used += actualCost;
|
|
85
|
+
drainQueue(entry);
|
|
86
|
+
},
|
|
87
|
+
async getUsage(userId, operation) {
|
|
88
|
+
const key = getKey(userId, operation);
|
|
89
|
+
const entry = getUsageEntry(key);
|
|
90
|
+
return {
|
|
91
|
+
userId,
|
|
92
|
+
operation,
|
|
93
|
+
used: entry.used,
|
|
94
|
+
limit: maxPerWindow,
|
|
95
|
+
windowStart: entry.windowStart,
|
|
96
|
+
queued: entry.queue.length,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=RateLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimit.js","sourceRoot":"","sources":["../../src/server/RateLimit.ts"],"names":[],"mappings":"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"}
|