create-fluxstack 1.10.1 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/.dockerignore +1 -2
  2. package/Dockerfile +8 -8
  3. package/LLMD/INDEX.md +64 -0
  4. package/LLMD/MAINTENANCE.md +197 -0
  5. package/LLMD/MIGRATION.md +156 -0
  6. package/LLMD/config/.gitkeep +1 -0
  7. package/LLMD/config/declarative-system.md +268 -0
  8. package/LLMD/config/environment-vars.md +327 -0
  9. package/LLMD/config/runtime-reload.md +401 -0
  10. package/LLMD/core/.gitkeep +1 -0
  11. package/LLMD/core/build-system.md +599 -0
  12. package/LLMD/core/framework-lifecycle.md +229 -0
  13. package/LLMD/core/plugin-system.md +451 -0
  14. package/LLMD/patterns/.gitkeep +1 -0
  15. package/LLMD/patterns/anti-patterns.md +297 -0
  16. package/LLMD/patterns/project-structure.md +264 -0
  17. package/LLMD/patterns/type-safety.md +440 -0
  18. package/LLMD/reference/.gitkeep +1 -0
  19. package/LLMD/reference/cli-commands.md +250 -0
  20. package/LLMD/reference/plugin-hooks.md +357 -0
  21. package/LLMD/reference/routing.md +39 -0
  22. package/LLMD/reference/troubleshooting.md +364 -0
  23. package/LLMD/resources/.gitkeep +1 -0
  24. package/LLMD/resources/controllers.md +465 -0
  25. package/LLMD/resources/live-components.md +703 -0
  26. package/LLMD/resources/live-rooms.md +482 -0
  27. package/LLMD/resources/live-upload.md +130 -0
  28. package/LLMD/resources/plugins-external.md +617 -0
  29. package/LLMD/resources/routes-eden.md +254 -0
  30. package/README.md +37 -17
  31. package/app/client/index.html +0 -1
  32. package/app/client/src/App.tsx +107 -150
  33. package/app/client/src/components/AppLayout.tsx +68 -0
  34. package/app/client/src/components/BackButton.tsx +13 -0
  35. package/app/client/src/components/DemoPage.tsx +20 -0
  36. package/app/client/src/components/LiveUploadWidget.tsx +204 -0
  37. package/app/client/src/lib/eden-api.ts +85 -60
  38. package/app/client/src/live/ChatDemo.tsx +107 -0
  39. package/app/client/src/live/CounterDemo.tsx +206 -0
  40. package/app/client/src/live/FormDemo.tsx +119 -0
  41. package/app/client/src/live/RoomChatDemo.tsx +161 -0
  42. package/app/client/src/live/UploadDemo.tsx +21 -0
  43. package/app/client/src/main.tsx +4 -1
  44. package/app/client/src/pages/ApiTestPage.tsx +108 -0
  45. package/app/client/src/pages/HomePage.tsx +76 -0
  46. package/app/server/app.ts +1 -4
  47. package/app/server/controllers/users.controller.ts +36 -44
  48. package/app/server/index.ts +25 -35
  49. package/app/server/live/LiveChat.ts +77 -0
  50. package/app/server/live/LiveCounter.ts +67 -0
  51. package/app/server/live/LiveForm.ts +63 -0
  52. package/app/server/live/LiveLocalCounter.ts +32 -0
  53. package/app/server/live/LiveRoomChat.ts +127 -0
  54. package/app/server/live/LiveUpload.ts +81 -0
  55. package/app/server/routes/index.ts +3 -1
  56. package/app/server/routes/room.routes.ts +117 -0
  57. package/app/server/routes/users.routes.ts +35 -27
  58. package/app/shared/types/index.ts +14 -2
  59. package/config/app.config.ts +2 -62
  60. package/config/client.config.ts +2 -95
  61. package/config/database.config.ts +2 -99
  62. package/config/fluxstack.config.ts +25 -45
  63. package/config/index.ts +57 -38
  64. package/config/monitoring.config.ts +2 -114
  65. package/config/plugins.config.ts +2 -80
  66. package/config/server.config.ts +2 -68
  67. package/config/services.config.ts +2 -130
  68. package/config/system/app.config.ts +29 -0
  69. package/config/system/build.config.ts +49 -0
  70. package/config/system/client.config.ts +68 -0
  71. package/config/system/database.config.ts +17 -0
  72. package/config/system/fluxstack.config.ts +114 -0
  73. package/config/{logger.config.ts → system/logger.config.ts} +3 -1
  74. package/config/system/monitoring.config.ts +114 -0
  75. package/config/system/plugins.config.ts +84 -0
  76. package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
  77. package/config/system/server.config.ts +68 -0
  78. package/config/system/services.config.ts +46 -0
  79. package/config/{system.config.ts → system/system.config.ts} +1 -1
  80. package/core/build/flux-plugins-generator.ts +325 -325
  81. package/core/build/index.ts +39 -27
  82. package/core/build/live-components-generator.ts +3 -3
  83. package/core/build/optimizer.ts +235 -235
  84. package/core/cli/command-registry.ts +6 -4
  85. package/core/cli/commands/build.ts +79 -0
  86. package/core/cli/commands/create.ts +54 -0
  87. package/core/cli/commands/dev.ts +101 -0
  88. package/core/cli/commands/help.ts +34 -0
  89. package/core/cli/commands/index.ts +34 -0
  90. package/core/cli/commands/make-plugin.ts +90 -0
  91. package/core/cli/commands/plugin-add.ts +197 -0
  92. package/core/cli/commands/plugin-deps.ts +2 -2
  93. package/core/cli/commands/plugin-list.ts +208 -0
  94. package/core/cli/commands/plugin-remove.ts +170 -0
  95. package/core/cli/generators/component.ts +769 -769
  96. package/core/cli/generators/controller.ts +1 -1
  97. package/core/cli/generators/index.ts +146 -146
  98. package/core/cli/generators/interactive.ts +227 -227
  99. package/core/cli/generators/plugin.ts +2 -2
  100. package/core/cli/generators/prompts.ts +82 -82
  101. package/core/cli/generators/route.ts +6 -6
  102. package/core/cli/generators/service.ts +2 -2
  103. package/core/cli/generators/template-engine.ts +4 -3
  104. package/core/cli/generators/types.ts +2 -2
  105. package/core/cli/generators/utils.ts +191 -191
  106. package/core/cli/index.ts +115 -686
  107. package/core/cli/plugin-discovery.ts +2 -2
  108. package/core/client/LiveComponentsProvider.tsx +60 -8
  109. package/core/client/api/eden.ts +183 -0
  110. package/core/client/api/index.ts +11 -0
  111. package/core/client/components/Live.tsx +104 -0
  112. package/core/client/fluxstack.ts +1 -9
  113. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  114. package/core/client/hooks/state-validator.ts +1 -1
  115. package/core/client/hooks/useAuth.ts +48 -48
  116. package/core/client/hooks/useChunkedUpload.ts +85 -35
  117. package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
  118. package/core/client/hooks/useLiveComponent.ts +800 -0
  119. package/core/client/hooks/useLiveUpload.ts +71 -0
  120. package/core/client/hooks/useRoom.ts +409 -0
  121. package/core/client/hooks/useRoomProxy.ts +382 -0
  122. package/core/client/index.ts +17 -68
  123. package/core/client/standalone-entry.ts +8 -0
  124. package/core/client/standalone.ts +74 -53
  125. package/core/client/state/createStore.ts +192 -192
  126. package/core/client/state/index.ts +14 -14
  127. package/core/config/index.ts +70 -291
  128. package/core/config/schema.ts +42 -723
  129. package/core/framework/client.ts +131 -131
  130. package/core/framework/index.ts +7 -7
  131. package/core/framework/server.ts +47 -40
  132. package/core/framework/types.ts +2 -2
  133. package/core/index.ts +23 -4
  134. package/core/live/ComponentRegistry.ts +3 -3
  135. package/core/live/types.ts +77 -0
  136. package/core/plugins/built-in/index.ts +134 -134
  137. package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
  138. package/core/plugins/built-in/live-components/index.ts +1 -1
  139. package/core/plugins/built-in/monitoring/index.ts +111 -47
  140. package/core/plugins/built-in/static/index.ts +1 -1
  141. package/core/plugins/built-in/swagger/index.ts +68 -265
  142. package/core/plugins/built-in/vite/index.ts +85 -185
  143. package/core/plugins/built-in/vite/vite-dev.ts +10 -16
  144. package/core/plugins/config.ts +9 -7
  145. package/core/plugins/dependency-manager.ts +31 -1
  146. package/core/plugins/discovery.ts +19 -7
  147. package/core/plugins/executor.ts +2 -2
  148. package/core/plugins/index.ts +203 -203
  149. package/core/plugins/manager.ts +27 -39
  150. package/core/plugins/module-resolver.ts +19 -8
  151. package/core/plugins/registry.ts +255 -19
  152. package/core/plugins/types.ts +20 -53
  153. package/core/server/framework.ts +66 -43
  154. package/core/server/index.ts +15 -15
  155. package/core/server/live/ComponentRegistry.ts +78 -71
  156. package/core/server/live/FileUploadManager.ts +23 -10
  157. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  158. package/core/server/live/LiveRoomManager.ts +261 -0
  159. package/core/server/live/RoomEventBus.ts +234 -0
  160. package/core/server/live/RoomStateManager.ts +172 -0
  161. package/core/server/live/StateSignature.ts +643 -643
  162. package/core/server/live/WebSocketConnectionManager.ts +30 -19
  163. package/core/server/live/auto-generated-components.ts +21 -9
  164. package/core/server/live/index.ts +14 -0
  165. package/core/server/live/websocket-plugin.ts +214 -67
  166. package/core/server/middleware/elysia-helpers.ts +7 -2
  167. package/core/server/middleware/errorHandling.ts +1 -1
  168. package/core/server/middleware/index.ts +31 -31
  169. package/core/server/plugins/database.ts +180 -180
  170. package/core/server/plugins/static-files-plugin.ts +69 -69
  171. package/core/server/plugins/swagger.ts +1 -1
  172. package/core/server/rooms/RoomBroadcaster.ts +357 -0
  173. package/core/server/rooms/RoomSystem.ts +463 -0
  174. package/core/server/rooms/index.ts +13 -0
  175. package/core/server/services/BaseService.ts +1 -1
  176. package/core/server/services/ServiceContainer.ts +1 -1
  177. package/core/server/services/index.ts +8 -8
  178. package/core/templates/create-project.ts +12 -12
  179. package/core/testing/index.ts +9 -9
  180. package/core/testing/setup.ts +73 -73
  181. package/core/types/api.ts +168 -168
  182. package/core/types/build.ts +219 -219
  183. package/core/types/config.ts +56 -26
  184. package/core/types/index.ts +4 -4
  185. package/core/types/plugin.ts +107 -107
  186. package/core/types/types.ts +353 -14
  187. package/core/utils/build-logger.ts +324 -324
  188. package/core/utils/config-schema.ts +480 -480
  189. package/core/utils/env.ts +2 -8
  190. package/core/utils/errors/codes.ts +114 -114
  191. package/core/utils/errors/handlers.ts +36 -1
  192. package/core/utils/errors/index.ts +49 -5
  193. package/core/utils/errors/middleware.ts +113 -113
  194. package/core/utils/helpers.ts +6 -16
  195. package/core/utils/index.ts +17 -17
  196. package/core/utils/logger/colors.ts +114 -114
  197. package/core/utils/logger/config.ts +13 -9
  198. package/core/utils/logger/formatter.ts +82 -82
  199. package/core/utils/logger/group-logger.ts +101 -101
  200. package/core/utils/logger/index.ts +6 -1
  201. package/core/utils/logger/stack-trace.ts +3 -1
  202. package/core/utils/logger/startup-banner.ts +82 -82
  203. package/core/utils/logger/winston-logger.ts +152 -152
  204. package/core/utils/monitoring/index.ts +211 -211
  205. package/core/utils/sync-version.ts +66 -66
  206. package/core/utils/version.ts +1 -1
  207. package/create-fluxstack.ts +8 -7
  208. package/package.json +12 -13
  209. package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
  210. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  211. package/plugins/crypto-auth/client/components/index.ts +11 -11
  212. package/plugins/crypto-auth/client/index.ts +11 -11
  213. package/plugins/crypto-auth/config/index.ts +1 -1
  214. package/plugins/crypto-auth/index.ts +4 -4
  215. package/plugins/crypto-auth/package.json +65 -65
  216. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  217. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  218. package/plugins/crypto-auth/server/index.ts +21 -21
  219. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
  220. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  221. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
  222. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
  223. package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
  224. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  225. package/tsconfig.api-strict.json +16 -0
  226. package/tsconfig.json +48 -52
  227. package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
  228. package/types/global.d.ts +29 -29
  229. package/types/vitest.d.ts +8 -8
  230. package/vite.config.ts +38 -62
  231. package/vitest.config.live.ts +10 -9
  232. package/vitest.config.ts +29 -17
  233. package/app/client/README.md +0 -69
  234. package/app/client/SIMPLIFICATION.md +0 -140
  235. package/app/client/frontend-only.ts +0 -12
  236. package/app/client/src/live/FileUploadExample.tsx +0 -359
  237. package/app/client/src/live/MinimalLiveClock.tsx +0 -47
  238. package/app/client/src/live/QuickUploadTest.tsx +0 -193
  239. package/app/client/tsconfig.app.json +0 -45
  240. package/app/client/tsconfig.json +0 -7
  241. package/app/client/zustand-setup.md +0 -65
  242. package/app/server/backend-only.ts +0 -18
  243. package/app/server/live/LiveClockComponent.ts +0 -215
  244. package/app/server/live/LiveFileUploadComponent.ts +0 -77
  245. package/app/server/routes/env-test.ts +0 -110
  246. package/core/client/hooks/index.ts +0 -7
  247. package/core/client/hooks/useHybridLiveComponent.ts +0 -685
  248. package/core/client/hooks/useTypedLiveComponent.ts +0 -133
  249. package/core/client/hooks/useWebSocket.ts +0 -361
  250. package/core/config/env.ts +0 -546
  251. package/core/config/loader.ts +0 -522
  252. package/core/config/runtime-config.ts +0 -327
  253. package/core/config/validator.ts +0 -540
  254. package/core/server/backend-entry.ts +0 -51
  255. package/core/server/standalone.ts +0 -106
  256. package/core/utils/regenerate-files.ts +0 -69
  257. package/fluxstack.config.ts +0 -354
@@ -0,0 +1,68 @@
1
+ import { Link, Outlet, useLocation } from 'react-router'
2
+ import { FaBook, FaGithub } from 'react-icons/fa'
3
+
4
+ const navItems = [
5
+ { to: '/', label: 'Home' },
6
+ { to: '/counter', label: 'Counter' },
7
+ { to: '/form', label: 'Form' },
8
+ { to: '/upload', label: 'Upload' },
9
+ { to: '/chat', label: 'Chat' },
10
+ { to: '/room-chat', label: 'Room Chat' },
11
+ { to: '/api-test', label: 'API Test' }
12
+ ]
13
+
14
+ export function AppLayout() {
15
+ const location = useLocation()
16
+
17
+ return (
18
+ <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900">
19
+ <header className="sticky top-0 z-10 backdrop-blur-md bg-slate-900/60 border-b border-white/10">
20
+ <div className="container mx-auto px-6 py-4 flex flex-wrap items-center justify-between gap-4">
21
+ <div className="text-white font-semibold tracking-wide">
22
+ FluxStack
23
+ </div>
24
+ <nav className="flex flex-wrap items-center gap-2">
25
+ {navItems.map((item) => {
26
+ const active = location.pathname === item.to
27
+ return (
28
+ <Link
29
+ key={item.to}
30
+ to={item.to}
31
+ className={`px-3 py-1.5 rounded-lg text-sm transition-all ${
32
+ active
33
+ ? 'bg-white/15 text-white'
34
+ : 'text-gray-300 hover:bg-white/10'
35
+ }`}
36
+ >
37
+ {item.label}
38
+ </Link>
39
+ )
40
+ })}
41
+ </nav>
42
+ <div className="flex items-center gap-2">
43
+ <a
44
+ href="/swagger"
45
+ target="_blank"
46
+ rel="noopener noreferrer"
47
+ className="inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
48
+ >
49
+ <FaBook />
50
+ Docs
51
+ </a>
52
+ <a
53
+ href="https://github.com/MarcosBrendonDePaula/FluxStack"
54
+ target="_blank"
55
+ rel="noopener noreferrer"
56
+ className="inline-flex items-center gap-2 px-3 py-1.5 bg-white/10 border border-white/20 text-white rounded-lg text-sm hover:bg-white/20 transition-all"
57
+ >
58
+ <FaGithub />
59
+ GitHub
60
+ </a>
61
+ </div>
62
+ </div>
63
+ </header>
64
+
65
+ <Outlet />
66
+ </div>
67
+ )
68
+ }
@@ -0,0 +1,13 @@
1
+ import { useNavigate } from 'react-router'
2
+
3
+ export function BackButton() {
4
+ const navigate = useNavigate()
5
+ return (
6
+ <button
7
+ onClick={() => navigate(-1)}
8
+ className="px-4 py-2 bg-white/10 backdrop-blur-sm border border-white/20 text-white rounded-lg font-medium hover:bg-white/20 transition-all"
9
+ >
10
+ ← Voltar
11
+ </button>
12
+ )
13
+ }
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react'
2
+ import { BackButton } from './BackButton'
3
+
4
+ export function DemoPage({ children, note }: { children: ReactNode; note?: ReactNode }) {
5
+ return (
6
+ <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex flex-col items-center justify-center px-4">
7
+ <div className="mb-8">
8
+ <BackButton />
9
+ </div>
10
+ <div className="w-full max-w-6xl">
11
+ {children}
12
+ </div>
13
+ {note && (
14
+ <p className="mt-6 text-gray-400 text-sm max-w-md text-center">
15
+ {note}
16
+ </p>
17
+ )}
18
+ </div>
19
+ )
20
+ }
@@ -0,0 +1,204 @@
1
+ import { useEffect, useMemo, useState } from 'react'
2
+ import { useLiveChunkedUpload } from '@/core/client'
3
+ import type { LiveChunkedUploadOptions } from '@/core/client'
4
+ import type { FileUploadCompleteResponse } from '@core/types/types'
5
+ type LiveUploadActions = {
6
+ $componentId: string | null
7
+ $connected: boolean
8
+ $state: {
9
+ status: 'idle' | 'uploading' | 'complete' | 'error'
10
+ progress: number
11
+ fileName: string
12
+ fileSize: number
13
+ fileType: string
14
+ fileUrl: string
15
+ bytesUploaded: number
16
+ totalBytes: number
17
+ error: string | null
18
+ }
19
+ $error?: string | null
20
+ startUpload: (payload: { fileName: string; fileSize: number; fileType: string }) => Promise<any>
21
+ updateProgress: (payload: { progress: number; bytesUploaded: number; totalBytes: number }) => Promise<any>
22
+ completeUpload: (payload: { fileUrl: string }) => Promise<any>
23
+ failUpload: (payload: { error: string }) => Promise<any>
24
+ reset: () => Promise<any>
25
+ }
26
+
27
+ export interface LiveUploadWidgetProps {
28
+ live: LiveUploadActions
29
+ title?: string
30
+ description?: string
31
+ allowPreview?: boolean
32
+ options?: LiveChunkedUploadOptions
33
+ onComplete?: (response: FileUploadCompleteResponse) => void
34
+ }
35
+
36
+ export function LiveUploadWidget({
37
+ live,
38
+ title = 'Upload em Chunks',
39
+ description = 'Envio via WebSocket com Live Components e reatividade server-side.',
40
+ allowPreview = true,
41
+ options,
42
+ onComplete
43
+ }: LiveUploadWidgetProps) {
44
+ // live is expected to be a LiveUpload-compatible component
45
+ const [selectedFile, setSelectedFile] = useState<File | null>(null)
46
+ const [localError, setLocalError] = useState<string | null>(null)
47
+ const [previewUrl, setPreviewUrl] = useState<string | null>(null)
48
+
49
+ const mergedOptions = useMemo<LiveChunkedUploadOptions>(() => {
50
+ return {
51
+ allowedTypes: [],
52
+ maxFileSize: 500 * 1024 * 1024,
53
+ adaptiveChunking: true,
54
+ fileUrlResolver: (fileUrl) => fileUrl.startsWith('/uploads/') ? `/api${fileUrl}` : fileUrl,
55
+ onComplete,
56
+ ...options
57
+ }
58
+ }, [options, onComplete])
59
+
60
+ const {
61
+ uploading,
62
+ bytesUploaded,
63
+ totalBytes,
64
+ uploadFile,
65
+ cancelUpload,
66
+ reset
67
+ } = useLiveChunkedUpload(live, mergedOptions)
68
+
69
+ const canUpload = live.$connected && !!live.$componentId && !uploading
70
+
71
+ const handleSelectFile = (e: React.ChangeEvent<HTMLInputElement>) => {
72
+ const file = e.target.files?.[0] ?? null
73
+ setSelectedFile(file)
74
+ setLocalError(null)
75
+
76
+ if (previewUrl) {
77
+ URL.revokeObjectURL(previewUrl)
78
+ setPreviewUrl(null)
79
+ }
80
+
81
+ if (allowPreview && file && file.type.startsWith('image/')) {
82
+ setPreviewUrl(URL.createObjectURL(file))
83
+ }
84
+ }
85
+
86
+ useEffect(() => {
87
+ return () => {
88
+ if (previewUrl) {
89
+ URL.revokeObjectURL(previewUrl)
90
+ }
91
+ }
92
+ }, [previewUrl])
93
+
94
+ const handleStartUpload = async () => {
95
+ if (!selectedFile) {
96
+ setLocalError('Selecione um arquivo primeiro.')
97
+ return
98
+ }
99
+
100
+ if (!live.$connected || !live.$componentId) {
101
+ setLocalError('WebSocket ainda nao conectou. Tente novamente em alguns segundos.')
102
+ return
103
+ }
104
+
105
+ setLocalError(null)
106
+ await uploadFile(selectedFile)
107
+ }
108
+
109
+ const handleReset = async () => {
110
+ setSelectedFile(null)
111
+ setLocalError(null)
112
+ if (previewUrl) {
113
+ URL.revokeObjectURL(previewUrl)
114
+ setPreviewUrl(null)
115
+ }
116
+ await reset()
117
+ }
118
+
119
+ const resolvedUrl = live.$state.fileUrl
120
+
121
+ return (
122
+ <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-xl w-full mx-auto">
123
+ <h2 className="text-2xl font-bold text-white mb-2 text-center">
124
+ {title}
125
+ </h2>
126
+
127
+ <p className="text-gray-400 text-sm text-center mb-6">
128
+ {description}
129
+ </p>
130
+
131
+ <div className="space-y-4">
132
+ <input
133
+ type="file"
134
+ onChange={handleSelectFile}
135
+ className="w-full text-sm text-gray-300 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-white/10 file:text-white hover:file:bg-white/20"
136
+ disabled={!live.$connected}
137
+ />
138
+
139
+ <div className="flex gap-3">
140
+ <button
141
+ onClick={handleStartUpload}
142
+ disabled={!canUpload || !selectedFile}
143
+ className="flex-1 px-4 py-2 rounded-lg bg-emerald-500/20 border border-emerald-500/30 text-emerald-300 hover:bg-emerald-500/30 transition-all disabled:opacity-50"
144
+ >
145
+ Iniciar Upload
146
+ </button>
147
+ <button
148
+ onClick={cancelUpload}
149
+ disabled={!uploading}
150
+ className="px-4 py-2 rounded-lg bg-red-500/20 border border-red-500/30 text-red-300 hover:bg-red-500/30 transition-all disabled:opacity-50"
151
+ >
152
+ Cancelar
153
+ </button>
154
+ <button
155
+ onClick={handleReset}
156
+ className="px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white hover:bg-white/20 transition-all"
157
+ >
158
+ Reset
159
+ </button>
160
+ </div>
161
+
162
+ {(localError || live.$state.error || live.$error) && (
163
+ <div className="text-sm text-red-300 bg-red-500/10 border border-red-500/30 rounded-lg px-3 py-2">
164
+ {localError || live.$state.error || live.$error}
165
+ </div>
166
+ )}
167
+
168
+ <div className="bg-black/40 border border-white/10 rounded-xl p-4">
169
+ <div className="flex justify-between text-xs text-gray-400 mb-2">
170
+ <span>Status: {live.$state.status}</span>
171
+ <span>{Math.round(live.$state.progress)}%</span>
172
+ </div>
173
+ <div className="w-full h-3 bg-white/10 rounded-full overflow-hidden">
174
+ <div
175
+ className="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all"
176
+ style={{ width: `${live.$state.progress}%` }}
177
+ />
178
+ </div>
179
+ <div className="flex justify-between text-xs text-gray-500 mt-2">
180
+ <span>{live.$state.fileName || 'Nenhum arquivo selecionado'}</span>
181
+ <span>{bytesUploaded > 0 ? `${Math.round(bytesUploaded / 1024)} KB` : ''}{totalBytes > 0 ? ` / ${Math.round(totalBytes / 1024)} KB` : ''}</span>
182
+ </div>
183
+ </div>
184
+
185
+ {previewUrl && (
186
+ <div className="bg-white/5 border border-white/10 rounded-xl p-4">
187
+ <div className="text-xs text-gray-400 mb-2">Preview</div>
188
+ <img
189
+ src={previewUrl}
190
+ alt="Preview"
191
+ className="max-h-48 w-full object-contain rounded-lg border border-white/10"
192
+ />
193
+ </div>
194
+ )}
195
+
196
+ {resolvedUrl && live.$state.status === 'complete' && (
197
+ <div className="bg-emerald-500/10 border border-emerald-500/30 rounded-xl p-4 text-sm text-emerald-200">
198
+ Upload concluido: <a className="underline" href={resolvedUrl} target="_blank" rel="noopener noreferrer">abrir arquivo</a>
199
+ </div>
200
+ )}
201
+ </div>
202
+ </div>
203
+ )
204
+ }
@@ -1,74 +1,99 @@
1
- // Eden Treaty API Client - Full Type Inference
2
- import { treaty } from '@elysiajs/eden'
3
- import type { App } from '../../../server/app'
4
-
5
1
  /**
6
- * Get base URL dynamically
2
+ * Eden Treaty API Client
3
+ * Configured for this application
4
+ *
5
+ * This file extends the core Eden client with app-specific customizations.
6
+ * Modify the options below to change API behavior across the entire app.
7
7
  */
8
- export const getBaseUrl = () => {
9
- if (typeof window === 'undefined') return 'http://localhost:3000'
10
-
11
- // Always use current origin - works for both dev and production
12
- return window.location.origin
13
- }
14
8
 
15
- /**
16
- * Get auth token from localStorage (optional)
17
- */
18
- const getAuthToken = (): string | null => {
19
- if (typeof window === 'undefined') return null
20
- return localStorage.getItem('accessToken')
21
- }
9
+ import { createEdenClient, getErrorMessage } from '@/core/client/api'
10
+ import type { App } from '@server/app'
22
11
 
23
12
  /**
24
- * Create Eden Treaty client with authentication and logging
25
- * Based on official ElysiaJS Chan example
13
+ * API client with full type inference from server routes
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // GET request
18
+ * const { data, error } = await api.users.get()
19
+ *
20
+ * // POST request
21
+ * const { data, error } = await api.users.post({
22
+ * name: 'John',
23
+ * email: 'john@example.com'
24
+ * })
25
+ *
26
+ * // With query params
27
+ * const { data } = await api.users.get({ query: { page: 1, limit: 10 } })
28
+ *
29
+ * // With path params
30
+ * const { data } = await api.users({ id: 123 }).get()
31
+ * ```
26
32
  */
27
- export const api = treaty<App>(getBaseUrl(), {
28
- // Dynamic headers for every request
29
- headers(_path, _options) {
30
- const token = getAuthToken()
31
- return token ? { Authorization: `Bearer ${token}` } : undefined
32
- },
33
+ export const api = createEdenClient<App>({
34
+ // ┌─────────────────────────────────────────────────────────────────┐
35
+ // CUSTOMIZATION EXAMPLES │
36
+ // Uncomment and modify the options below as needed │
37
+ // └─────────────────────────────────────────────────────────────────┘
33
38
 
34
- // Custom fetcher with logging and error handling
35
- // Use 'as any' to bypass Bun's strict fetch type (includes preconnect property)
36
- fetcher: (async (url, init) => {
37
- if (import.meta.env.DEV) {
38
- console.log(`🌐 ${init?.method ?? 'GET'} ${url}`)
39
- }
39
+ // ── Authentication ──────────────────────────────────────────────────
40
+ // Custom token key (default: 'accessToken')
41
+ // tokenKey: 'authToken',
40
42
 
41
- const res = await fetch(url, init)
43
+ // Custom token getter (e.g., from a state manager)
44
+ // getAuthToken: () => {
45
+ // return useAuthStore.getState().token
46
+ // },
42
47
 
43
- if (import.meta.env.DEV) {
44
- console.log(`📡 ${url} ${res.status}`)
45
- }
48
+ // ── Unauthorized Handler ────────────────────────────────────────────
49
+ // Redirect to login on 401
50
+ // onUnauthorized: () => {
51
+ // localStorage.removeItem('accessToken')
52
+ // window.location.href = '/login'
53
+ // },
46
54
 
47
- // Auto-logout on 401
48
- if (res.status === 401) {
49
- console.warn('🔒 Token expired')
50
- localStorage.removeItem('accessToken')
51
- // window.location.href = '/login' // Uncomment if you have auth
52
- }
55
+ // Show toast notification on 401
56
+ // onUnauthorized: () => {
57
+ // toast.error('Session expired. Please login again.')
58
+ // router.push('/login')
59
+ // },
53
60
 
54
- return res
55
- }) as typeof fetch
56
- }).api // expose the generated API object
61
+ // ── Base URL ────────────────────────────────────────────────────────
62
+ // Custom base URL (e.g., for different environments)
63
+ // getBaseUrl: () => {
64
+ // if (import.meta.env.PROD) return 'https://api.myapp.com'
65
+ // return 'http://localhost:3000'
66
+ // },
57
67
 
58
- /**
59
- * Get user-friendly error message
60
- */
61
- export function getErrorMessage(error: unknown): string {
62
- // Eden Treaty error format
63
- if (error && typeof error === 'object' && 'value' in error) {
64
- const edenError = error as { value?: { message?: string; userMessage?: string } }
65
- return edenError.value?.userMessage || edenError.value?.message || 'An error occurred'
66
- }
68
+ // ── Logging ─────────────────────────────────────────────────────────
69
+ // Force enable/disable logging (default: auto based on DEV mode)
70
+ // enableLogging: true,
71
+ })
67
72
 
68
- // Standard Error
69
- if (error instanceof Error) {
70
- return error.message
71
- }
73
+ // Re-export utility for convenience
74
+ export { getErrorMessage }
72
75
 
73
- return 'An unexpected error occurred'
74
- }
76
+ // ┌─────────────────────────────────────────────────────────────────────┐
77
+ // │ ADVANCED: Creating Multiple API Clients │
78
+ // │ │
79
+ // │ You can create additional clients for different purposes: │
80
+ // └─────────────────────────────────────────────────────────────────────┘
81
+ //
82
+ // // Public API (no auth)
83
+ // export const publicApi = createEdenClient<App>({
84
+ // getAuthToken: () => null,
85
+ // })
86
+ //
87
+ // // Admin API (different token)
88
+ // export const adminApi = createEdenClient<App>({
89
+ // tokenKey: 'adminToken',
90
+ // onUnauthorized: () => {
91
+ // window.location.href = '/admin/login'
92
+ // },
93
+ // })
94
+ //
95
+ // // External API (different base URL)
96
+ // import type { ExternalApp } from './external-types'
97
+ // export const externalApi = createEdenClient<ExternalApp>({
98
+ // getBaseUrl: () => 'https://external-api.com',
99
+ // })
@@ -0,0 +1,107 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react'
2
+ import { Live } from '@/core/client'
3
+ import { LiveChat } from '@server/live/LiveChat'
4
+
5
+ export function ChatDemo() {
6
+ const [text, setText] = useState('')
7
+ const [user, setUser] = useState('')
8
+ const containerRef = useRef<HTMLDivElement | null>(null)
9
+ const wasNearBottomRef = useRef(true)
10
+ const defaultUser = useMemo(() => {
11
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
12
+ return `user-${crypto.randomUUID().slice(0, 6)}`
13
+ }
14
+ return `user-${Math.random().toString(36).slice(2, 8)}`
15
+ }, [])
16
+
17
+ const chat = Live.use(LiveChat, {
18
+ room: 'global-chat',
19
+ initialState: LiveChat.defaultState,
20
+ persistState: false
21
+ })
22
+
23
+ const handleSend = async () => {
24
+ if (!text.trim()) return
25
+ const finalUser = user.trim() || defaultUser
26
+ await chat.sendMessage({ user: finalUser, text })
27
+ setText('')
28
+ }
29
+
30
+ useEffect(() => {
31
+ const el = containerRef.current
32
+ if (!el) return
33
+
34
+ if (wasNearBottomRef.current) {
35
+ el.scrollTop = el.scrollHeight
36
+ }
37
+ }, [chat.$state.messages.length])
38
+
39
+ const handleScroll = () => {
40
+ const el = containerRef.current
41
+ if (!el) return
42
+ const distance = el.scrollHeight - (el.scrollTop + el.clientHeight)
43
+ wasNearBottomRef.current = distance < 80
44
+ }
45
+
46
+ return (
47
+ <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 w-full mx-auto">
48
+ <h2 className="text-2xl font-bold text-white mb-2 text-center">Chat Compartilhado</h2>
49
+ <p className="text-gray-400 text-sm text-center mb-4">
50
+ Sala global em tempo real. Abra em várias abas para testar.
51
+ </p>
52
+
53
+ <div className="flex flex-wrap items-center gap-2 mb-4 text-xs">
54
+ <span className={`px-3 py-1 rounded-full ${chat.$connected ? 'bg-emerald-500/20 text-emerald-300' : 'bg-red-500/20 text-red-300'}`}>
55
+ {chat.$connected ? 'Conectado' : 'Desconectado'}
56
+ </span>
57
+ <span className="text-gray-400">Você: {user.trim() || defaultUser}</span>
58
+ </div>
59
+
60
+ <div className="mb-4">
61
+ <label className="block text-xs text-gray-400 mb-2">Seu nome</label>
62
+ <input
63
+ value={user}
64
+ onChange={(e) => setUser(e.target.value)}
65
+ placeholder={`Ex: ${defaultUser}`}
66
+ className="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
67
+ />
68
+ </div>
69
+
70
+ <div
71
+ ref={containerRef}
72
+ onScroll={handleScroll}
73
+ className="bg-black/40 border border-white/10 rounded-xl p-5 h-[28rem] overflow-auto space-y-3"
74
+ >
75
+ {chat.$state.messages.length === 0 && (
76
+ <div className="text-gray-500 text-sm text-center">Nenhuma mensagem ainda</div>
77
+ )}
78
+ {chat.$state.messages.map((m) => (
79
+ <div key={m.id} className="text-sm">
80
+ <span className="text-purple-300 font-semibold">{m.user}</span>
81
+ <span className="text-gray-500 text-xs ml-2">{new Date(m.timestamp).toLocaleTimeString()}</span>
82
+ <div className="text-gray-200">{m.text}</div>
83
+ </div>
84
+ ))}
85
+ </div>
86
+
87
+ <div className="mt-4 flex gap-2">
88
+ <input
89
+ value={text}
90
+ onChange={(e) => setText(e.target.value)}
91
+ onKeyDown={(e) => {
92
+ if (e.key === 'Enter') void handleSend()
93
+ }}
94
+ placeholder="Digite uma mensagem..."
95
+ className="flex-1 px-4 py-2 rounded-lg bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
96
+ />
97
+ <button
98
+ onClick={handleSend}
99
+ disabled={!chat.$connected}
100
+ className="px-4 py-2 rounded-lg bg-purple-500/20 border border-purple-500/30 text-purple-200 hover:bg-purple-500/30 transition-all disabled:opacity-50"
101
+ >
102
+ Enviar
103
+ </button>
104
+ </div>
105
+ </div>
106
+ )
107
+ }