create-fluxstack 1.10.1 → 1.12.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 (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 +242 -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 +285 -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,285 @@
1
+ // LiveRoomChat - Chat usando o sistema de salas $room
2
+
3
+ import { LiveComponent, type FluxStackWebSocket } from '@core/types/types'
4
+
5
+ // Componente Cliente (Ctrl+Click para navegar)
6
+ import type { RoomChatDemo as _Client } from '@client/src/live/RoomChatDemo'
7
+
8
+ // Tipos
9
+ export interface ChatMessage {
10
+ id: string
11
+ user: string
12
+ text: string
13
+ timestamp: number
14
+ }
15
+
16
+ export interface RoomInfo {
17
+ id: string
18
+ name: string
19
+ }
20
+
21
+ export class LiveRoomChat extends LiveComponent<typeof LiveRoomChat.defaultState> {
22
+ static componentName = 'LiveRoomChat'
23
+ static defaultState = {
24
+ username: '',
25
+ activeRoom: null as string | null,
26
+ rooms: [] as RoomInfo[],
27
+ messages: {} as Record<string, ChatMessage[]>,
28
+ typingUsers: {} as Record<string, string[]>
29
+ }
30
+ protected roomType = 'room-chat'
31
+
32
+ private typingTimers = new Map<string, NodeJS.Timeout>()
33
+
34
+ constructor(
35
+ initialState: Partial<typeof LiveRoomChat.defaultState> = {},
36
+ ws: FluxStackWebSocket,
37
+ options?: { room?: string; userId?: string }
38
+ ) {
39
+ super(initialState, ws, options)
40
+ }
41
+
42
+ // ===== Gerenciamento de Salas =====
43
+
44
+ /**
45
+ * Entrar em uma sala de chat
46
+ */
47
+ async joinRoom(payload: { roomId: string; roomName?: string }) {
48
+ const { roomId, roomName } = payload
49
+
50
+ // Entrar na sala
51
+ this.$room(roomId).join()
52
+
53
+ // Escutar mensagens novas de outros usuários
54
+ this.$room(roomId).on('message:new', (msg: ChatMessage) => {
55
+ this.addMessageToState(roomId, msg)
56
+ })
57
+
58
+ // Escutar usuários digitando
59
+ this.$room(roomId).on('user:typing', (data: { user: string; typing: boolean }) => {
60
+ this.updateTypingUsers(roomId, data.user, data.typing)
61
+ })
62
+
63
+ // Atualizar lista de salas e inicializar mensagens
64
+ const rooms = [
65
+ ...this.state.rooms.filter(r => r.id !== roomId),
66
+ { id: roomId, name: roomName || roomId }
67
+ ]
68
+
69
+ this.setState({
70
+ rooms,
71
+ activeRoom: roomId,
72
+ messages: {
73
+ ...this.state.messages,
74
+ [roomId]: this.state.messages[roomId] || []
75
+ },
76
+ typingUsers: {
77
+ ...this.state.typingUsers,
78
+ [roomId]: []
79
+ }
80
+ })
81
+
82
+ return {
83
+ success: true,
84
+ roomId,
85
+ rooms: this.$rooms
86
+ }
87
+ }
88
+
89
+ private addMessageToState(roomId: string, msg: ChatMessage) {
90
+ const currentMessages = this.state.messages[roomId] || []
91
+ const newMessages = [...currentMessages, msg].slice(-100)
92
+
93
+ this.setState({
94
+ messages: {
95
+ ...this.state.messages,
96
+ [roomId]: newMessages
97
+ }
98
+ })
99
+ }
100
+
101
+ private updateTypingUsers(roomId: string, user: string, typing: boolean) {
102
+ const current = this.state.typingUsers[roomId] || []
103
+ let updated: string[]
104
+
105
+ if (typing && !current.includes(user)) {
106
+ updated = [...current, user]
107
+ } else if (!typing) {
108
+ updated = current.filter(u => u !== user)
109
+ } else {
110
+ return // Sem mudança
111
+ }
112
+
113
+ this.setState({
114
+ typingUsers: {
115
+ ...this.state.typingUsers,
116
+ [roomId]: updated
117
+ }
118
+ })
119
+ }
120
+
121
+ /**
122
+ * Sair de uma sala
123
+ */
124
+ async leaveRoom(payload: { roomId: string }) {
125
+ const { roomId } = payload
126
+
127
+ // Sair da sala
128
+ this.$room(roomId).leave()
129
+
130
+ // Atualizar lista
131
+ const rooms = this.state.rooms.filter(r => r.id !== roomId)
132
+ const activeRoom = this.state.activeRoom === roomId
133
+ ? (rooms[0]?.id || null)
134
+ : this.state.activeRoom
135
+
136
+ // Remover mensagens e typing da sala
137
+ const { [roomId]: _msgs, ...restMessages } = this.state.messages
138
+ const { [roomId]: _typing, ...restTyping } = this.state.typingUsers
139
+
140
+ this.setState({
141
+ rooms,
142
+ activeRoom,
143
+ messages: restMessages,
144
+ typingUsers: restTyping
145
+ })
146
+
147
+ return {
148
+ success: true,
149
+ rooms: this.$rooms
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Trocar sala ativa (para UI)
155
+ */
156
+ async switchRoom(payload: { roomId: string }) {
157
+ if (!this.$rooms.includes(payload.roomId)) {
158
+ throw new Error('Not in this room')
159
+ }
160
+
161
+ this.setState({ activeRoom: payload.roomId })
162
+ return { success: true }
163
+ }
164
+
165
+ // ===== Mensagens =====
166
+
167
+ /**
168
+ * Enviar mensagem para sala ativa
169
+ */
170
+ async sendMessage(payload: { text: string; roomId?: string }) {
171
+ const roomId = payload.roomId || this.state.activeRoom
172
+ if (!roomId) throw new Error('No active room')
173
+
174
+ const text = payload.text?.trim()
175
+ if (!text) throw new Error('Message cannot be empty')
176
+ if (text.length > 1000) throw new Error('Message too long')
177
+
178
+ const message: ChatMessage = {
179
+ id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
180
+ user: this.state.username || 'Anônimo',
181
+ text,
182
+ timestamp: Date.now()
183
+ }
184
+
185
+ // Adicionar ao meu estado local
186
+ this.addMessageToState(roomId, message)
187
+
188
+ // Emitir para outros na sala (eles vão receber via $room.on)
189
+ this.$room(roomId).emit('message:new', message)
190
+
191
+ // Parar de digitar
192
+ this.stopTyping({ roomId })
193
+
194
+ return { success: true, message }
195
+ }
196
+
197
+ /**
198
+ * Indicar que está digitando
199
+ */
200
+ async startTyping(payload: { roomId?: string }) {
201
+ const roomId = payload.roomId || this.state.activeRoom
202
+ if (!roomId) return { success: false }
203
+
204
+ // Emitir evento
205
+ this.$room(roomId).emit('user:typing', {
206
+ user: this.state.username,
207
+ typing: true
208
+ })
209
+
210
+ // Limpar timer anterior
211
+ const existingTimer = this.typingTimers.get(roomId)
212
+ if (existingTimer) clearTimeout(existingTimer)
213
+
214
+ // Auto-parar após 3 segundos
215
+ const timer = setTimeout(() => {
216
+ this.stopTyping({ roomId })
217
+ }, 3000)
218
+ this.typingTimers.set(roomId, timer)
219
+
220
+ return { success: true }
221
+ }
222
+
223
+ /**
224
+ * Parar de digitar
225
+ */
226
+ async stopTyping(payload: { roomId?: string }) {
227
+ const roomId = payload.roomId || this.state.activeRoom
228
+ if (!roomId) return { success: false }
229
+
230
+ // Limpar timer
231
+ const timer = this.typingTimers.get(roomId)
232
+ if (timer) {
233
+ clearTimeout(timer)
234
+ this.typingTimers.delete(roomId)
235
+ }
236
+
237
+ // Emitir evento
238
+ this.$room(roomId).emit('user:typing', {
239
+ user: this.state.username,
240
+ typing: false
241
+ })
242
+
243
+ return { success: true }
244
+ }
245
+
246
+ // ===== Configuração =====
247
+
248
+ /**
249
+ * Definir nome do usuário
250
+ */
251
+ async setUsername(payload: { username: string }) {
252
+ const username = payload.username?.trim()
253
+ if (!username) throw new Error('Username required')
254
+ if (username.length > 30) throw new Error('Username too long')
255
+
256
+ this.setState({ username })
257
+ return { success: true, username }
258
+ }
259
+
260
+ /**
261
+ * Obter estado de uma sala
262
+ */
263
+ async getRoomState(payload: { roomId: string }) {
264
+ const state = this.$room(payload.roomId).state
265
+ return {
266
+ success: true,
267
+ state: {
268
+ messages: state.messages || [],
269
+ typingUsers: state.typingUsers || []
270
+ }
271
+ }
272
+ }
273
+
274
+ // ===== Cleanup =====
275
+
276
+ public destroy() {
277
+ // Limpar timers
278
+ for (const timer of this.typingTimers.values()) {
279
+ clearTimeout(timer)
280
+ }
281
+ this.typingTimers.clear()
282
+
283
+ super.destroy()
284
+ }
285
+ }
@@ -0,0 +1,81 @@
1
+ // LiveUpload - Estado de upload chunked + sincronização UI
2
+
3
+ import { LiveComponent } from '@core/types/types'
4
+
5
+ // Componente Cliente (Ctrl+Click para navegar)
6
+ import type { UploadDemo as _Client } from '@client/src/live/UploadDemo'
7
+
8
+ export class LiveUpload extends LiveComponent<typeof LiveUpload.defaultState> {
9
+ static componentName = 'LiveUpload'
10
+ static defaultState = {
11
+ status: 'idle' as 'idle' | 'uploading' | 'complete' | 'error',
12
+ progress: 0,
13
+ fileName: '',
14
+ fileSize: 0,
15
+ fileType: '',
16
+ fileUrl: '',
17
+ bytesUploaded: 0,
18
+ totalBytes: 0,
19
+ error: null as string | null
20
+ }
21
+
22
+ async startUpload(payload: { fileName: string; fileSize: number; fileType: string }) {
23
+ const normalized = payload.fileName.toLowerCase()
24
+ if (normalized.includes('..') || normalized.includes('/') || normalized.includes('\\')) {
25
+ throw new Error('Invalid file name')
26
+ }
27
+
28
+ // All file types allowed - no extension blocking
29
+ // Security note: Configure allowed extensions per your application needs
30
+
31
+ this.setState({
32
+ status: 'uploading',
33
+ progress: 0,
34
+ fileName: payload.fileName,
35
+ fileSize: payload.fileSize,
36
+ fileType: payload.fileType,
37
+ fileUrl: '',
38
+ bytesUploaded: 0,
39
+ totalBytes: payload.fileSize,
40
+ error: null
41
+ })
42
+
43
+ return { success: true }
44
+ }
45
+
46
+ async updateProgress(payload: { progress: number; bytesUploaded: number; totalBytes: number }) {
47
+ const progress = Math.max(0, Math.min(100, payload.progress))
48
+ this.setState({
49
+ progress,
50
+ bytesUploaded: payload.bytesUploaded,
51
+ totalBytes: payload.totalBytes
52
+ })
53
+
54
+ return { success: true, progress }
55
+ }
56
+
57
+ async completeUpload(payload: { fileUrl: string }) {
58
+ this.setState({
59
+ status: 'complete',
60
+ progress: 100,
61
+ fileUrl: payload.fileUrl,
62
+ error: null
63
+ })
64
+
65
+ return { success: true }
66
+ }
67
+
68
+ async failUpload(payload: { error: string }) {
69
+ this.setState({
70
+ status: 'error',
71
+ error: payload.error || 'Upload failed'
72
+ })
73
+
74
+ return { success: true }
75
+ }
76
+
77
+ async reset() {
78
+ this.setState({ ...LiveUpload.defaultState })
79
+ return { success: true }
80
+ }
81
+ }
@@ -1,5 +1,6 @@
1
1
  import { Elysia, t } from "elysia"
2
2
  import { usersRoutes } from "./users.routes"
3
+ import { roomRoutes } from "./room.routes"
3
4
 
4
5
  export const apiRoutes = new Elysia({ prefix: "/api" })
5
6
  .get("/", () => ({ message: "🔥 Hot Reload funcionando! FluxStack API v1.4.0 ⚡" }), {
@@ -32,5 +33,6 @@ export const apiRoutes = new Elysia({ prefix: "/api" })
32
33
  description: 'Returns the current health status of the API server'
33
34
  }
34
35
  })
35
- // Register users routes
36
+ // Register routes
36
37
  .use(usersRoutes)
38
+ .use(roomRoutes)
@@ -0,0 +1,117 @@
1
+ // 🔥 Room API Routes - Enviar mensagens para salas via HTTP
2
+ //
3
+ // Permite que sistemas externos (webhooks, outros serviços, etc)
4
+ // enviem mensagens para salas de chat via API REST
5
+
6
+ import { Elysia, t } from 'elysia'
7
+ import { liveRoomManager } from '@core/server/live/LiveRoomManager'
8
+ import { roomEvents } from '@core/server/live/RoomEventBus'
9
+
10
+ export const roomRoutes = new Elysia({ prefix: '/rooms' })
11
+
12
+ // Enviar mensagem para uma sala
13
+ .post('/:roomId/messages', ({ params, body }) => {
14
+ const { roomId } = params
15
+ const { user, text } = body
16
+
17
+ const message = {
18
+ id: `api-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
19
+ user: user || 'API Bot',
20
+ text,
21
+ timestamp: Date.now()
22
+ }
23
+
24
+ // Emitir evento para a sala
25
+ // Isso vai:
26
+ // 1. Notificar handlers server-side via roomEvents
27
+ // 2. Broadcast via WebSocket para frontends
28
+ const notified = liveRoomManager.emitToRoom(roomId, 'message:new', message)
29
+
30
+ return {
31
+ success: true,
32
+ message,
33
+ notified,
34
+ roomId
35
+ }
36
+ }, {
37
+ params: t.Object({
38
+ roomId: t.String({ description: 'ID da sala (ex: geral, tech, random)' })
39
+ }),
40
+ body: t.Object({
41
+ user: t.Optional(t.String({ description: 'Nome do usuário (opcional, default: API Bot)' })),
42
+ text: t.String({ description: 'Texto da mensagem', minLength: 1, maxLength: 1000 })
43
+ }),
44
+ response: t.Object({
45
+ success: t.Boolean(),
46
+ message: t.Object({
47
+ id: t.String(),
48
+ user: t.String(),
49
+ text: t.String(),
50
+ timestamp: t.Number()
51
+ }),
52
+ notified: t.Number(),
53
+ roomId: t.String()
54
+ }),
55
+ detail: {
56
+ tags: ['Rooms'],
57
+ summary: 'Enviar mensagem para sala',
58
+ description: 'Envia uma mensagem para todos os usuários conectados em uma sala específica'
59
+ }
60
+ })
61
+
62
+ // Emitir evento customizado para uma sala
63
+ .post('/:roomId/emit', ({ params, body }) => {
64
+ const { roomId } = params
65
+ const { event, data } = body
66
+
67
+ const notified = liveRoomManager.emitToRoom(roomId, event, data)
68
+
69
+ return {
70
+ success: true,
71
+ event,
72
+ notified,
73
+ roomId
74
+ }
75
+ }, {
76
+ params: t.Object({
77
+ roomId: t.String({ description: 'ID da sala' })
78
+ }),
79
+ body: t.Object({
80
+ event: t.String({ description: 'Nome do evento (ex: user:typing, notification)' }),
81
+ data: t.Any({ description: 'Dados do evento' })
82
+ }),
83
+ response: t.Object({
84
+ success: t.Boolean(),
85
+ event: t.String(),
86
+ notified: t.Number(),
87
+ roomId: t.String()
88
+ }),
89
+ detail: {
90
+ tags: ['Rooms'],
91
+ summary: 'Emitir evento customizado',
92
+ description: 'Emite um evento customizado para todos os componentes em uma sala'
93
+ }
94
+ })
95
+
96
+ // Obter estatísticas das salas
97
+ .get('/stats', () => {
98
+ const roomStats = liveRoomManager.getStats()
99
+ const eventStats = roomEvents.getStats()
100
+
101
+ return {
102
+ success: true,
103
+ rooms: roomStats,
104
+ events: eventStats
105
+ }
106
+ }, {
107
+ response: t.Object({
108
+ success: t.Boolean(),
109
+ rooms: t.Any(),
110
+ events: t.Any()
111
+ }),
112
+ detail: {
113
+ tags: ['Rooms'],
114
+ summary: 'Estatísticas das salas',
115
+ description: 'Retorna estatísticas sobre salas ativas e eventos registrados'
116
+ }
117
+ })
@@ -1,5 +1,6 @@
1
1
  import { Elysia, t } from 'elysia'
2
- import { UsersController } from '@/app/server/controllers/users.controller'
2
+ import { UsersController } from '@app/server/controllers/users.controller'
3
+ import type { CreateUserRequest } from '@app/shared/types'
3
4
 
4
5
  // ===== Request/Response Schemas =====
5
6
 
@@ -77,9 +78,7 @@ const ErrorResponseSchema = t.Object({
77
78
  */
78
79
  export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
79
80
  // GET /users - Get all users
80
- .get('/', async () => {
81
- return await UsersController.getUsers()
82
- }, {
81
+ .get('/', async () => UsersController.getUsers(), {
83
82
  detail: {
84
83
  summary: 'Get All Users',
85
84
  description: 'Retrieves a list of all registered users',
@@ -90,19 +89,18 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
90
89
 
91
90
  // GET /users/:id - Get user by ID
92
91
  .get('/:id', async ({ params, set }) => {
93
- const id = parseInt(params.id)
92
+ const id = Number(params.id)
94
93
 
95
- // Handle invalid ID
96
- if (isNaN(id)) {
94
+ if (!Number.isFinite(id)) {
97
95
  set.status = 400
98
- return { error: 'ID inválido' }
96
+ return { success: false, error: 'ID invalido' }
99
97
  }
100
98
 
101
99
  const result = await UsersController.getUserById(id)
102
100
 
103
- if (!result) {
101
+ if (!result.success) {
104
102
  set.status = 404
105
- return { error: 'Usuário não encontrado' }
103
+ return result
106
104
  }
107
105
 
108
106
  return result
@@ -124,17 +122,17 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
124
122
 
125
123
  // POST /users - Create new user
126
124
  .post('/', async ({ body, set }) => {
127
- // Validate required fields
128
- if (!body.name || !body.email) {
125
+ const payload = body as CreateUserRequest
126
+
127
+ if (!payload.name || !payload.email) {
129
128
  set.status = 400
130
129
  return {
131
130
  success: false,
132
- error: 'Nome e email são obrigatórios'
131
+ error: 'Nome e email sao obrigatorios'
133
132
  }
134
133
  }
135
134
 
136
- // Validate name length
137
- if (body.name.length < 2) {
135
+ if (payload.name.trim().length < 2) {
138
136
  set.status = 400
139
137
  return {
140
138
  success: false,
@@ -142,23 +140,28 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
142
140
  }
143
141
  }
144
142
 
145
- // Validate email format
146
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
147
- if (!emailRegex.test(body.email)) {
143
+ const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/
144
+ if (!emailRegex.test(payload.email)) {
148
145
  set.status = 400
149
146
  return {
150
147
  success: false,
151
- error: 'Email inválido'
148
+ error: 'Email invalido'
152
149
  }
153
150
  }
154
151
 
155
- const result = await UsersController.createUser(body)
152
+ const sanitizedPayload: CreateUserRequest = {
153
+ name: payload.name.trim(),
154
+ email: payload.email.trim()
155
+ }
156
+
157
+ const result = await UsersController.createUser(sanitizedPayload)
156
158
 
157
- // If email is duplicate, still return 200 but with success: false
158
159
  if (!result.success) {
160
+ set.status = 409
159
161
  return result
160
162
  }
161
163
 
164
+ set.status = 201
162
165
  return result
163
166
  }, {
164
167
  detail: {
@@ -168,30 +171,34 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
168
171
  },
169
172
  body: CreateUserRequestSchema,
170
173
  response: {
171
- 200: CreateUserResponseSchema,
174
+ 201: CreateUserResponseSchema,
172
175
  400: t.Object({
173
176
  success: t.Literal(false),
174
177
  error: t.String()
178
+ }),
179
+ 409: t.Object({
180
+ success: t.Literal(false),
181
+ error: t.String()
175
182
  })
176
183
  }
177
184
  })
178
185
 
179
186
  // DELETE /users/:id - Delete user
180
187
  .delete('/:id', async ({ params, set }) => {
181
- const id = parseInt(params.id)
188
+ const id = Number(params.id)
182
189
 
183
- if (isNaN(id)) {
190
+ if (!Number.isFinite(id)) {
184
191
  set.status = 400
185
192
  return {
186
193
  success: false,
187
- message: 'ID inválido'
194
+ message: 'ID invalido'
188
195
  }
189
196
  }
190
197
 
191
198
  const result = await UsersController.deleteUser(id)
192
199
 
193
200
  if (!result.success) {
194
- // Don't set 404 status, just return success: false
201
+ set.status = 404
195
202
  return result
196
203
  }
197
204
 
@@ -210,6 +217,7 @@ export const usersRoutes = new Elysia({ prefix: '/users', tags: ['Users'] })
210
217
  400: t.Object({
211
218
  success: t.Literal(false),
212
219
  message: t.String()
213
- })
220
+ }),
221
+ 404: DeleteUserResponseSchema
214
222
  }
215
223
  })
@@ -11,8 +11,20 @@ export interface CreateUserRequest {
11
11
  email: string
12
12
  }
13
13
 
14
- export interface UserResponse {
14
+ export interface UserListResponse {
15
+ success: true
16
+ users: User[]
17
+ count: number
18
+ }
19
+
20
+ export interface UserDetailResponse {
15
21
  success: boolean
16
22
  user?: User
23
+ error?: string
24
+ }
25
+
26
+ export interface MutationResponse {
27
+ success: boolean
17
28
  message?: string
18
- }
29
+ error?: string
30
+ }