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,71 @@
1
+ import { useMemo } from 'react'
2
+ import { Live } from '../components/Live'
3
+ import { useLiveChunkedUpload } from './useLiveChunkedUpload'
4
+ import type { LiveChunkedUploadOptions } from './useLiveChunkedUpload'
5
+ import type { FileUploadCompleteResponse } from '@core/types/types'
6
+ import { LiveUpload } from '@server/live/LiveUpload'
7
+
8
+ export interface UseLiveUploadOptions {
9
+ live?: {
10
+ room?: string
11
+ userId?: string
12
+ autoMount?: boolean
13
+ debug?: boolean
14
+ }
15
+ upload?: LiveChunkedUploadOptions
16
+ onProgress?: (progress: number, bytesUploaded: number, totalBytes: number) => void
17
+ onComplete?: (response: FileUploadCompleteResponse) => void
18
+ onError?: (error: string) => void
19
+ }
20
+
21
+ export function useLiveUpload(options: UseLiveUploadOptions = {}) {
22
+ const { live: liveOptions, upload: uploadOptions, onProgress, onComplete, onError } = options
23
+
24
+ const live = Live.use(LiveUpload, {
25
+ initialState: LiveUpload.defaultState,
26
+ ...liveOptions
27
+ })
28
+
29
+ const mergedUploadOptions = useMemo<LiveChunkedUploadOptions>(() => {
30
+ return {
31
+ allowedTypes: [],
32
+ maxFileSize: 500 * 1024 * 1024,
33
+ adaptiveChunking: true,
34
+ fileUrlResolver: (fileUrl) => fileUrl.startsWith('/uploads/') ? `/api${fileUrl}` : fileUrl,
35
+ onProgress,
36
+ onComplete,
37
+ onError,
38
+ ...uploadOptions
39
+ }
40
+ }, [onProgress, onComplete, onError, uploadOptions])
41
+
42
+ const upload = useLiveChunkedUpload(live, mergedUploadOptions)
43
+
44
+ const startUpload = useMemo(() => {
45
+ return async (file: File) => {
46
+ if (!live.$connected || !live.$componentId) {
47
+ const msg = 'WebSocket nao conectado. Tente novamente.'
48
+ onError?.(msg)
49
+ await live.failUpload({ error: msg })
50
+ return
51
+ }
52
+ await upload.uploadFile(file)
53
+ }
54
+ }, [live, upload, onError])
55
+
56
+ return {
57
+ live,
58
+ state: live.$state,
59
+ status: live.$state.status,
60
+ connected: live.$connected,
61
+ componentId: live.$componentId,
62
+ uploading: upload.uploading,
63
+ progress: upload.progress,
64
+ bytesUploaded: upload.bytesUploaded,
65
+ totalBytes: upload.totalBytes,
66
+ error: live.$state.error,
67
+ startUpload,
68
+ cancelUpload: upload.cancelUpload,
69
+ reset: upload.reset
70
+ }
71
+ }
@@ -0,0 +1,409 @@
1
+ // 🔥 FluxStack useRoom - Hook para conectar a salas do backend
2
+
3
+ import { useState, useEffect, useCallback, useRef } from 'react'
4
+
5
+ // Tipos de mensagens do servidor
6
+ interface RoomMessage {
7
+ type: 'room:event' | 'room:state' | 'room:system'
8
+ roomId: string
9
+ event: string
10
+ data: any
11
+ timestamp: number
12
+ senderId?: string
13
+ }
14
+
15
+ // Mensagens do cliente para o servidor
16
+ interface ClientMessage {
17
+ type: 'room:join' | 'room:leave' | 'room:emit'
18
+ roomId: string
19
+ event?: string
20
+ data?: any
21
+ timestamp: number
22
+ }
23
+
24
+ // Opções do hook
25
+ interface UseRoomOptions<TState> {
26
+ initialState: TState
27
+ autoJoin?: boolean
28
+ onConnect?: () => void
29
+ onDisconnect?: () => void
30
+ onError?: (error: string) => void
31
+ onStateChange?: (state: TState, prevState: TState) => void
32
+ }
33
+
34
+ // Retorno do hook
35
+ interface UseRoomReturn<TState, TEvents extends Record<string, any>> {
36
+ // Estado
37
+ state: TState
38
+
39
+ // Status
40
+ connected: boolean
41
+ joined: boolean
42
+ roomId: string | null
43
+
44
+ // Ações
45
+ join: (roomId: string) => void
46
+ leave: () => void
47
+ emit: <K extends keyof TEvents>(event: K, data: TEvents[K]) => void
48
+
49
+ // Listeners
50
+ on: <K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void) => () => void
51
+ onSystem: (event: string, handler: (data: any) => void) => () => void
52
+ }
53
+
54
+ // Gerenciador de conexão WebSocket (singleton por URL)
55
+ class RoomWebSocketManager {
56
+ private static instances = new Map<string, RoomWebSocketManager>()
57
+
58
+ private ws: WebSocket | null = null
59
+ private url: string
60
+ private reconnectAttempts = 0
61
+ private maxReconnectAttempts = 5
62
+ private reconnectDelay = 1000
63
+ private listeners = new Map<string, Set<(msg: RoomMessage) => void>>()
64
+ private connectionListeners = new Set<{
65
+ onConnect?: () => void
66
+ onDisconnect?: () => void
67
+ onError?: (error: string) => void
68
+ }>()
69
+ private messageQueue: ClientMessage[] = []
70
+ private isConnected = false
71
+
72
+ static getInstance(url: string): RoomWebSocketManager {
73
+ if (!this.instances.has(url)) {
74
+ this.instances.set(url, new RoomWebSocketManager(url))
75
+ }
76
+ return this.instances.get(url)!
77
+ }
78
+
79
+ private constructor(url: string) {
80
+ this.url = url
81
+ this.connect()
82
+ }
83
+
84
+ private connect(): void {
85
+ try {
86
+ this.ws = new WebSocket(this.url)
87
+
88
+ this.ws.onopen = () => {
89
+ console.log('[RoomWS] Connected')
90
+ this.isConnected = true
91
+ this.reconnectAttempts = 0
92
+
93
+ // Enviar mensagens em fila
94
+ for (const msg of this.messageQueue) {
95
+ this.send(msg)
96
+ }
97
+ this.messageQueue = []
98
+
99
+ // Notificar listeners
100
+ for (const listener of this.connectionListeners) {
101
+ listener.onConnect?.()
102
+ }
103
+ }
104
+
105
+ this.ws.onmessage = (event) => {
106
+ try {
107
+ const msg: RoomMessage = JSON.parse(event.data)
108
+ this.handleMessage(msg)
109
+ } catch (error) {
110
+ console.error('[RoomWS] Failed to parse message:', error)
111
+ }
112
+ }
113
+
114
+ this.ws.onclose = () => {
115
+ console.log('[RoomWS] Disconnected')
116
+ this.isConnected = false
117
+
118
+ for (const listener of this.connectionListeners) {
119
+ listener.onDisconnect?.()
120
+ }
121
+
122
+ // Tentar reconectar
123
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
124
+ this.reconnectAttempts++
125
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
126
+ console.log(`[RoomWS] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`)
127
+ setTimeout(() => this.connect(), delay)
128
+ }
129
+ }
130
+
131
+ this.ws.onerror = (error) => {
132
+ console.error('[RoomWS] Error:', error)
133
+ for (const listener of this.connectionListeners) {
134
+ listener.onError?.('WebSocket error')
135
+ }
136
+ }
137
+ } catch (error) {
138
+ console.error('[RoomWS] Failed to connect:', error)
139
+ }
140
+ }
141
+
142
+ private handleMessage(msg: RoomMessage): void {
143
+ // Chave para listeners: roomId:event
144
+ const key = `${msg.roomId}:${msg.event}`
145
+ const listeners = this.listeners.get(key)
146
+
147
+ if (listeners) {
148
+ for (const handler of listeners) {
149
+ try {
150
+ handler(msg)
151
+ } catch (error) {
152
+ console.error('[RoomWS] Handler error:', error)
153
+ }
154
+ }
155
+ }
156
+
157
+ // Também notificar listeners de todos eventos da sala
158
+ const roomKey = `${msg.roomId}:*`
159
+ const roomListeners = this.listeners.get(roomKey)
160
+ if (roomListeners) {
161
+ for (const handler of roomListeners) {
162
+ try {
163
+ handler(msg)
164
+ } catch (error) {
165
+ console.error('[RoomWS] Handler error:', error)
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ send(message: ClientMessage): void {
172
+ if (this.isConnected && this.ws?.readyState === WebSocket.OPEN) {
173
+ this.ws.send(JSON.stringify(message))
174
+ } else {
175
+ this.messageQueue.push(message)
176
+ }
177
+ }
178
+
179
+ subscribe(roomId: string, event: string, handler: (msg: RoomMessage) => void): () => void {
180
+ const key = `${roomId}:${event}`
181
+
182
+ if (!this.listeners.has(key)) {
183
+ this.listeners.set(key, new Set())
184
+ }
185
+ this.listeners.get(key)!.add(handler)
186
+
187
+ return () => {
188
+ this.listeners.get(key)?.delete(handler)
189
+ if (this.listeners.get(key)?.size === 0) {
190
+ this.listeners.delete(key)
191
+ }
192
+ }
193
+ }
194
+
195
+ addConnectionListener(listener: {
196
+ onConnect?: () => void
197
+ onDisconnect?: () => void
198
+ onError?: (error: string) => void
199
+ }): () => void {
200
+ this.connectionListeners.add(listener)
201
+
202
+ // Se já conectado, chamar onConnect imediatamente
203
+ if (this.isConnected) {
204
+ listener.onConnect?.()
205
+ }
206
+
207
+ return () => {
208
+ this.connectionListeners.delete(listener)
209
+ }
210
+ }
211
+
212
+ getConnectionStatus(): boolean {
213
+ return this.isConnected
214
+ }
215
+ }
216
+
217
+ // Hook principal
218
+ export function useRoom<
219
+ TDef extends { state: any; events: Record<string, any> }
220
+ >(
221
+ wsUrl: string,
222
+ options: UseRoomOptions<TDef['state']>
223
+ ): UseRoomReturn<TDef['state'], TDef['events']> {
224
+ const [state, setState] = useState<TDef['state']>(options.initialState)
225
+ const [connected, setConnected] = useState(false)
226
+ const [joined, setJoined] = useState(false)
227
+ const [roomId, setRoomId] = useState<string | null>(null)
228
+
229
+ const wsManager = useRef<RoomWebSocketManager | null>(null)
230
+ const eventHandlers = useRef<Map<string, Set<(data: any) => void>>>(new Map())
231
+ const unsubscribes = useRef<(() => void)[]>([])
232
+
233
+ // Inicializar WebSocket manager
234
+ useEffect(() => {
235
+ wsManager.current = RoomWebSocketManager.getInstance(wsUrl)
236
+
237
+ const unsub = wsManager.current.addConnectionListener({
238
+ onConnect: () => {
239
+ setConnected(true)
240
+ options.onConnect?.()
241
+ },
242
+ onDisconnect: () => {
243
+ setConnected(false)
244
+ setJoined(false)
245
+ options.onDisconnect?.()
246
+ },
247
+ onError: options.onError
248
+ })
249
+
250
+ setConnected(wsManager.current.getConnectionStatus())
251
+
252
+ return () => {
253
+ unsub()
254
+ // Limpar todas as subscriptions
255
+ for (const unsub of unsubscribes.current) {
256
+ unsub()
257
+ }
258
+ }
259
+ }, [wsUrl])
260
+
261
+ // Join room
262
+ const join = useCallback((newRoomId: string) => {
263
+ if (!wsManager.current) return
264
+
265
+ // Sair da sala anterior se existir
266
+ if (roomId && roomId !== newRoomId) {
267
+ leave()
268
+ }
269
+
270
+ setRoomId(newRoomId)
271
+
272
+ // Enviar mensagem de join
273
+ wsManager.current.send({
274
+ type: 'room:join',
275
+ roomId: newRoomId,
276
+ data: { initialState: options.initialState },
277
+ timestamp: Date.now()
278
+ })
279
+
280
+ // Subscrever em todos os eventos da sala
281
+ const unsub = wsManager.current.subscribe(newRoomId, '*', (msg) => {
282
+ // Atualizar estado
283
+ if (msg.event === '$state:sync' || msg.event === '$state:update') {
284
+ setState(prev => {
285
+ const newState = { ...prev, ...msg.data.state }
286
+ options.onStateChange?.(newState, prev)
287
+ return newState
288
+ })
289
+ } else if (msg.event === '$state:change') {
290
+ setState(prev => {
291
+ const newState = { ...prev, [msg.data.path]: msg.data.newValue }
292
+ options.onStateChange?.(newState, prev)
293
+ return newState
294
+ })
295
+ }
296
+
297
+ // Chamar handlers registrados
298
+ const handlers = eventHandlers.current.get(msg.event)
299
+ if (handlers) {
300
+ for (const handler of handlers) {
301
+ handler(msg.data)
302
+ }
303
+ }
304
+ })
305
+
306
+ unsubscribes.current.push(unsub)
307
+ setJoined(true)
308
+ }, [roomId, options.initialState])
309
+
310
+ // Leave room
311
+ const leave = useCallback(() => {
312
+ if (!wsManager.current || !roomId) return
313
+
314
+ wsManager.current.send({
315
+ type: 'room:leave',
316
+ roomId,
317
+ timestamp: Date.now()
318
+ })
319
+
320
+ // Limpar subscriptions
321
+ for (const unsub of unsubscribes.current) {
322
+ unsub()
323
+ }
324
+ unsubscribes.current = []
325
+ eventHandlers.current.clear()
326
+
327
+ setJoined(false)
328
+ setRoomId(null)
329
+ setState(options.initialState)
330
+ }, [roomId, options.initialState])
331
+
332
+ // Emit event
333
+ const emit = useCallback(<K extends keyof TDef['events']>(
334
+ event: K,
335
+ data: TDef['events'][K]
336
+ ) => {
337
+ if (!wsManager.current || !roomId) return
338
+
339
+ wsManager.current.send({
340
+ type: 'room:emit',
341
+ roomId,
342
+ event: event as string,
343
+ data,
344
+ timestamp: Date.now()
345
+ })
346
+ }, [roomId])
347
+
348
+ // Subscribe to event
349
+ const on = useCallback(<K extends keyof TDef['events']>(
350
+ event: K,
351
+ handler: (data: TDef['events'][K]) => void
352
+ ): (() => void) => {
353
+ const eventKey = event as string
354
+
355
+ if (!eventHandlers.current.has(eventKey)) {
356
+ eventHandlers.current.set(eventKey, new Set())
357
+ }
358
+ eventHandlers.current.get(eventKey)!.add(handler)
359
+
360
+ return () => {
361
+ eventHandlers.current.get(eventKey)?.delete(handler)
362
+ }
363
+ }, [])
364
+
365
+ // Subscribe to system event
366
+ const onSystem = useCallback((
367
+ event: string,
368
+ handler: (data: any) => void
369
+ ): (() => void) => {
370
+ const eventKey = `$${event}`
371
+
372
+ if (!eventHandlers.current.has(eventKey)) {
373
+ eventHandlers.current.set(eventKey, new Set())
374
+ }
375
+ eventHandlers.current.get(eventKey)!.add(handler)
376
+
377
+ return () => {
378
+ eventHandlers.current.get(eventKey)?.delete(handler)
379
+ }
380
+ }, [])
381
+
382
+ // Auto-join
383
+ useEffect(() => {
384
+ if (options.autoJoin && connected && !joined && roomId) {
385
+ join(roomId)
386
+ }
387
+ }, [options.autoJoin, connected, joined, roomId, join])
388
+
389
+ return {
390
+ state,
391
+ connected,
392
+ joined,
393
+ roomId,
394
+ join,
395
+ leave,
396
+ emit,
397
+ on,
398
+ onSystem
399
+ }
400
+ }
401
+
402
+ // Helper para criar hook tipado
403
+ export function createRoomHook<
404
+ TDef extends { state: any; events: Record<string, any> }
405
+ >(wsUrl: string) {
406
+ return (options: UseRoomOptions<TDef['state']>) => useRoom<TDef>(wsUrl, options)
407
+ }
408
+
409
+ export type { UseRoomOptions, UseRoomReturn, RoomMessage, ClientMessage }