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,482 @@
1
+ # Live Room System
2
+
3
+ **Version:** 1.11.0 | **Updated:** 2025-02-09
4
+
5
+ ## Quick Facts
6
+
7
+ - Server-side room management for real-time communication
8
+ - Multiple rooms per component supported
9
+ - Events propagate to all room members automatically
10
+ - HTTP API integration for external systems (webhooks, bots)
11
+ - Type-safe event handling with TypeScript
12
+
13
+ ## Overview
14
+
15
+ The Room System enables real-time communication between Live Components. It's **server-side first**, meaning:
16
+
17
+ 1. Server controls room membership and event routing
18
+ 2. Each component updates its own client via `setState()`
19
+ 3. External systems can emit events via HTTP API
20
+
21
+ ## Core API
22
+
23
+ ### Server-Side ($room)
24
+
25
+ ```typescript
26
+ // app/server/live/MyComponent.ts
27
+ import { LiveComponent } from '@core/types/types'
28
+
29
+ export class MyComponent extends LiveComponent<typeof defaultState> {
30
+
31
+ // Join a room
32
+ this.$room('room-id').join()
33
+
34
+ // Leave a room
35
+ this.$room('room-id').leave()
36
+
37
+ // Emit event to all members (except self)
38
+ this.$room('room-id').emit('event-name', { data: 'value' })
39
+
40
+ // Listen for events from other members
41
+ this.$room('room-id').on('event-name', (data) => {
42
+ // Handle event, update local state
43
+ this.setState({ ... })
44
+ })
45
+
46
+ // Get room state
47
+ const state = this.$room('room-id').state
48
+
49
+ // Set room state (broadcasts to all)
50
+ this.$room('room-id').setState({ key: 'value' })
51
+
52
+ // List all joined rooms
53
+ const rooms = this.$rooms // ['room-1', 'room-2']
54
+ }
55
+ ```
56
+
57
+ ### Default Room
58
+
59
+ If component has a default room (via `options.room`), you can use shorthand:
60
+
61
+ ```typescript
62
+ // Using default room
63
+ this.$room.emit('event', data)
64
+ this.$room.on('event', handler)
65
+
66
+ // Equivalent to:
67
+ this.$room('default-room-id').emit('event', data)
68
+ ```
69
+
70
+ ## Complete Example: Chat Component
71
+
72
+ ### Server Component
73
+
74
+ ```typescript
75
+ // app/server/live/LiveRoomChat.ts
76
+ import { LiveComponent } from '@core/types/types'
77
+
78
+ export interface ChatMessage {
79
+ id: string
80
+ user: string
81
+ text: string
82
+ timestamp: number
83
+ }
84
+
85
+ export const defaultState = {
86
+ username: '',
87
+ activeRoom: null as string | null,
88
+ rooms: [] as { id: string; name: string }[],
89
+ messages: {} as Record<string, ChatMessage[]>,
90
+ typingUsers: {} as Record<string, string[]>
91
+ }
92
+
93
+ export class LiveRoomChat extends LiveComponent<typeof defaultState> {
94
+ static defaultState = defaultState
95
+
96
+ constructor(initialState: Partial<typeof defaultState>, ws: any, options?: { room?: string; userId?: string }) {
97
+ super({ ...defaultState, ...initialState }, ws, options)
98
+ }
99
+
100
+ // Join a chat room
101
+ async joinRoom(payload: { roomId: string; roomName?: string }) {
102
+ const { roomId, roomName } = payload
103
+
104
+ // 1. Join the room on server
105
+ this.$room(roomId).join()
106
+
107
+ // 2. Listen for messages from OTHER users
108
+ this.$room(roomId).on('message:new', (msg: ChatMessage) => {
109
+ this.addMessageToState(roomId, msg)
110
+ })
111
+
112
+ // 3. Listen for typing events
113
+ this.$room(roomId).on('user:typing', (data: { user: string; typing: boolean }) => {
114
+ this.updateTypingUsers(roomId, data.user, data.typing)
115
+ })
116
+
117
+ // 4. Update local state (syncs to frontend)
118
+ this.setState({
119
+ activeRoom: roomId,
120
+ rooms: [...this.state.rooms, { id: roomId, name: roomName || roomId }],
121
+ messages: { ...this.state.messages, [roomId]: [] },
122
+ typingUsers: { ...this.state.typingUsers, [roomId]: [] }
123
+ })
124
+
125
+ return { success: true, roomId }
126
+ }
127
+
128
+ // Send message
129
+ async sendMessage(payload: { text: string }) {
130
+ const roomId = this.state.activeRoom
131
+ if (!roomId) throw new Error('No active room')
132
+
133
+ const message: ChatMessage = {
134
+ id: `msg-${Date.now()}`,
135
+ user: this.state.username,
136
+ text: payload.text,
137
+ timestamp: Date.now()
138
+ }
139
+
140
+ // 1. Add to MY state (syncs to MY frontend)
141
+ this.addMessageToState(roomId, message)
142
+
143
+ // 2. Emit to OTHERS (they receive via $room.on)
144
+ this.$room(roomId).emit('message:new', message)
145
+
146
+ return { success: true, message }
147
+ }
148
+
149
+ // Helper: add message to state
150
+ private addMessageToState(roomId: string, msg: ChatMessage) {
151
+ const messages = this.state.messages[roomId] || []
152
+ this.setState({
153
+ messages: {
154
+ ...this.state.messages,
155
+ [roomId]: [...messages, msg].slice(-100) // Keep last 100
156
+ }
157
+ })
158
+ }
159
+
160
+ // Helper: update typing users
161
+ private updateTypingUsers(roomId: string, user: string, typing: boolean) {
162
+ const current = this.state.typingUsers[roomId] || []
163
+ const updated = typing
164
+ ? [...current.filter(u => u !== user), user]
165
+ : current.filter(u => u !== user)
166
+
167
+ this.setState({
168
+ typingUsers: { ...this.state.typingUsers, [roomId]: updated }
169
+ })
170
+ }
171
+
172
+ // Start typing indicator
173
+ async startTyping() {
174
+ const roomId = this.state.activeRoom
175
+ if (!roomId) return { success: false }
176
+
177
+ this.$room(roomId).emit('user:typing', {
178
+ user: this.state.username,
179
+ typing: true
180
+ })
181
+
182
+ return { success: true }
183
+ }
184
+
185
+ // Leave room
186
+ async leaveRoom(payload: { roomId: string }) {
187
+ const { roomId } = payload
188
+
189
+ this.$room(roomId).leave()
190
+
191
+ const { [roomId]: _, ...restMessages } = this.state.messages
192
+ const { [roomId]: __, ...restTyping } = this.state.typingUsers
193
+
194
+ this.setState({
195
+ rooms: this.state.rooms.filter(r => r.id !== roomId),
196
+ activeRoom: this.state.activeRoom === roomId ? null : this.state.activeRoom,
197
+ messages: restMessages,
198
+ typingUsers: restTyping
199
+ })
200
+
201
+ return { success: true }
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### Frontend Component
207
+
208
+ ```typescript
209
+ // app/client/src/live/RoomChatDemo.tsx
210
+ import { Live } from '@/core/client'
211
+ import { LiveRoomChat, defaultState } from '@server/live/LiveRoomChat'
212
+
213
+ export function RoomChatDemo() {
214
+ const [text, setText] = useState('')
215
+
216
+ // Connect to Live Component
217
+ const chat = Live.use(LiveRoomChat, {
218
+ initialState: { ...defaultState, username: 'User123' }
219
+ })
220
+
221
+ // State comes directly from server
222
+ const activeRoom = chat.$state.activeRoom
223
+ const messages = activeRoom ? (chat.$state.messages[activeRoom] || []) : []
224
+ const typingUsers = activeRoom ? (chat.$state.typingUsers[activeRoom] || []) : []
225
+
226
+ // Join room
227
+ const handleJoinRoom = async (roomId: string) => {
228
+ await chat.joinRoom({ roomId, roomName: roomId })
229
+ }
230
+
231
+ // Send message
232
+ const handleSend = async () => {
233
+ if (!text.trim()) return
234
+ await chat.sendMessage({ text })
235
+ setText('')
236
+ }
237
+
238
+ return (
239
+ <div>
240
+ {/* Room list */}
241
+ <div>
242
+ {['geral', 'tech', 'random'].map(roomId => (
243
+ <button key={roomId} onClick={() => handleJoinRoom(roomId)}>
244
+ {roomId} {chat.$rooms.includes(roomId) && '(joined)'}
245
+ </button>
246
+ ))}
247
+ </div>
248
+
249
+ {/* Messages */}
250
+ <div>
251
+ {messages.map(msg => (
252
+ <div key={msg.id}>
253
+ <strong>{msg.user}:</strong> {msg.text}
254
+ </div>
255
+ ))}
256
+ </div>
257
+
258
+ {/* Typing indicator */}
259
+ {typingUsers.length > 0 && (
260
+ <div>{typingUsers.join(', ')} typing...</div>
261
+ )}
262
+
263
+ {/* Input */}
264
+ <input
265
+ value={text}
266
+ onChange={e => {
267
+ setText(e.target.value)
268
+ chat.startTyping()
269
+ }}
270
+ onKeyDown={e => e.key === 'Enter' && handleSend()}
271
+ />
272
+ <button onClick={handleSend}>Send</button>
273
+ </div>
274
+ )
275
+ }
276
+ ```
277
+
278
+ ## HTTP API Integration
279
+
280
+ Send messages from external systems via REST API:
281
+
282
+ ### Routes
283
+
284
+ ```typescript
285
+ // app/server/routes/room.routes.ts
286
+ import { Elysia, t } from 'elysia'
287
+ import { liveRoomManager } from '@core/server/live/LiveRoomManager'
288
+
289
+ export const roomRoutes = new Elysia({ prefix: '/rooms' })
290
+
291
+ // Send message to room
292
+ .post('/:roomId/messages', ({ params, body }) => {
293
+ const message = {
294
+ id: `api-${Date.now()}`,
295
+ user: body.user || 'API Bot',
296
+ text: body.text,
297
+ timestamp: Date.now()
298
+ }
299
+
300
+ const notified = liveRoomManager.emitToRoom(
301
+ params.roomId,
302
+ 'message:new',
303
+ message
304
+ )
305
+
306
+ return { success: true, message, notified }
307
+ }, {
308
+ params: t.Object({ roomId: t.String() }),
309
+ body: t.Object({
310
+ user: t.Optional(t.String()),
311
+ text: t.String()
312
+ })
313
+ })
314
+
315
+ // Emit custom event
316
+ .post('/:roomId/emit', ({ params, body }) => {
317
+ const notified = liveRoomManager.emitToRoom(
318
+ params.roomId,
319
+ body.event,
320
+ body.data
321
+ )
322
+ return { success: true, notified }
323
+ }, {
324
+ params: t.Object({ roomId: t.String() }),
325
+ body: t.Object({
326
+ event: t.String(),
327
+ data: t.Any()
328
+ })
329
+ })
330
+
331
+ // Get stats
332
+ .get('/stats', () => liveRoomManager.getStats())
333
+ ```
334
+
335
+ ### Usage Examples
336
+
337
+ ```bash
338
+ # Send message via curl
339
+ curl -X POST http://localhost:3000/api/rooms/geral/messages \
340
+ -H "Content-Type: application/json" \
341
+ -d '{"user": "Webhook Bot", "text": "New deployment completed!"}'
342
+
343
+ # Emit custom event
344
+ curl -X POST http://localhost:3000/api/rooms/tech/emit \
345
+ -H "Content-Type: application/json" \
346
+ -d '{"event": "notification", "data": {"type": "alert", "message": "Server restarted"}}'
347
+
348
+ # Get room stats
349
+ curl http://localhost:3000/api/rooms/stats
350
+ ```
351
+
352
+ ## Event Flow Diagram
353
+
354
+ ```
355
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
356
+ │ Frontend A │ │ Server │ │ Frontend B │
357
+ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
358
+ │ │ │
359
+ │ sendMessage() │ │
360
+ │──────────────────>│ │
361
+ │ │ │
362
+ │ │ 1. setState() │
363
+ │ │ (sync to A) │
364
+ │<──────────────────│ │
365
+ │ │ │
366
+ │ │ 2. $room.emit() │
367
+ │ │ (to others) │
368
+ │ │ │
369
+ │ │ 3. B's handler │
370
+ │ │ receives event │
371
+ │ │ │
372
+ │ │ 4. B's setState() │
373
+ │ │ (sync to B) │
374
+ │ │──────────────────>│
375
+ │ │ │
376
+ ```
377
+
378
+ ## Room Manager API
379
+
380
+ Direct access to room manager for advanced use cases:
381
+
382
+ ```typescript
383
+ import { liveRoomManager } from '@core/server/live/LiveRoomManager'
384
+
385
+ // Join component to room
386
+ liveRoomManager.joinRoom(componentId, roomId, ws, initialState)
387
+
388
+ // Leave room
389
+ liveRoomManager.leaveRoom(componentId, roomId)
390
+
391
+ // Emit event to room
392
+ const count = liveRoomManager.emitToRoom(roomId, event, data, excludeComponentId)
393
+
394
+ // Update room state
395
+ liveRoomManager.setRoomState(roomId, updates, excludeComponentId)
396
+
397
+ // Get room state
398
+ const state = liveRoomManager.getRoomState(roomId)
399
+
400
+ // Check membership
401
+ const isIn = liveRoomManager.isInRoom(componentId, roomId)
402
+
403
+ // Get component's rooms
404
+ const rooms = liveRoomManager.getComponentRooms(componentId)
405
+
406
+ // Cleanup on disconnect
407
+ liveRoomManager.cleanupComponent(componentId)
408
+
409
+ // Get statistics
410
+ const stats = liveRoomManager.getStats()
411
+ ```
412
+
413
+ ## Room Event Bus
414
+
415
+ For server-side event handling:
416
+
417
+ ```typescript
418
+ import { roomEvents } from '@core/server/live/RoomEventBus'
419
+
420
+ // Subscribe to events
421
+ const unsubscribe = roomEvents.on(
422
+ 'room', // type
423
+ 'geral', // roomId
424
+ 'message', // event
425
+ componentId, // subscriber
426
+ (data) => { // handler
427
+ console.log('Received:', data)
428
+ }
429
+ )
430
+
431
+ // Emit events
432
+ roomEvents.emit('room', 'geral', 'message', { text: 'Hello' }, excludeId)
433
+
434
+ // Cleanup
435
+ roomEvents.unsubscribeAll(componentId)
436
+ roomEvents.clearRoom('room', 'geral')
437
+
438
+ // Stats
439
+ const stats = roomEvents.getStats()
440
+ ```
441
+
442
+ ## Use Cases
443
+
444
+ | Use Case | Description |
445
+ |----------|-------------|
446
+ | **Chat** | Multi-room chat with typing indicators |
447
+ | **Notifications** | Send alerts to specific groups |
448
+ | **Collaboration** | Real-time document editing |
449
+ | **Gaming** | Multiplayer game state sync |
450
+ | **Dashboards** | Live metrics and alerts |
451
+ | **Webhooks** | External events to rooms |
452
+ | **Presence** | Online/offline status |
453
+
454
+ ## Best Practices
455
+
456
+ **DO:**
457
+ - Use `setState()` to sync state to your own frontend
458
+ - Use `$room.emit()` to notify other components
459
+ - Register handlers with `$room.on()` in `joinRoom`
460
+ - Clean up with `$room.leave()` when leaving
461
+ - Use HTTP API for external integrations
462
+
463
+ **DON'T:**
464
+ - Rely on `$room.emit()` to update your own frontend
465
+ - Forget to handle events from other users
466
+ - Store non-serializable data in room state
467
+ - Skip error handling in event handlers
468
+
469
+ ## Files Reference
470
+
471
+ | File | Purpose |
472
+ |------|---------|
473
+ | `core/server/live/LiveRoomManager.ts` | Room membership and broadcasting |
474
+ | `core/server/live/RoomEventBus.ts` | Server-side event pub/sub |
475
+ | `core/types/types.ts` | `$room` and `$rooms` implementation |
476
+ | `app/server/routes/room.routes.ts` | HTTP API for rooms |
477
+
478
+ ## Related
479
+
480
+ - [Live Components](./live-components.md) - Base component system
481
+ - [Routes with Eden Treaty](./routes-eden.md) - HTTP API patterns
482
+ - [Type Safety](../patterns/type-safety.md) - TypeScript patterns
@@ -0,0 +1,130 @@
1
+ # Live Upload (Chunked Upload via WebSocket)
2
+
3
+ **Version:** 1.11.0 | **Updated:** 2026-02-08
4
+
5
+ ## Overview
6
+
7
+ FluxStack supports chunked file upload over the Live Components WebSocket. The
8
+ server tracks progress and assembles the file in `uploads/`. The client streams
9
+ chunks without loading the entire file into memory.
10
+
11
+ ## Server: LiveUpload Component
12
+
13
+ ```typescript
14
+ // app/server/live/LiveUpload.ts
15
+ import { LiveComponent } from '@core/types/types'
16
+ import { liveUploadDefaultState, type LiveUploadState } from '@app/shared'
17
+
18
+ export const defaultState: LiveUploadState = liveUploadDefaultState
19
+
20
+ export class LiveUpload extends LiveComponent<LiveUploadState> {
21
+ static defaultState = defaultState
22
+
23
+ constructor(initialState: Partial<typeof defaultState>, ws: any, options?: { room?: string; userId?: string }) {
24
+ super({ ...defaultState, ...initialState }, ws, options)
25
+ }
26
+
27
+ async startUpload(payload: { fileName: string; fileSize: number; fileType: string }) {
28
+ const normalized = payload.fileName.toLowerCase()
29
+ if (normalized.includes('..') || normalized.includes('/') || normalized.includes('\\')) {
30
+ throw new Error('Invalid file name')
31
+ }
32
+
33
+ const ext = normalized.includes('.') ? normalized.split('.').pop() || '' : ''
34
+ const blocked = ['exe', 'bat', 'cmd', 'sh', 'ps1', 'msi', 'jar']
35
+ if (ext && blocked.includes(ext)) {
36
+ throw new Error(`File extension not allowed: .${ext}`)
37
+ }
38
+
39
+ this.setState({
40
+ status: 'uploading',
41
+ progress: 0,
42
+ fileName: payload.fileName,
43
+ fileSize: payload.fileSize,
44
+ fileType: payload.fileType,
45
+ fileUrl: '',
46
+ bytesUploaded: 0,
47
+ totalBytes: payload.fileSize,
48
+ error: null
49
+ })
50
+
51
+ return { success: true }
52
+ }
53
+
54
+ async updateProgress(payload: { progress: number; bytesUploaded: number; totalBytes: number }) {
55
+ const progress = Math.max(0, Math.min(100, payload.progress))
56
+ this.setState({
57
+ progress,
58
+ bytesUploaded: payload.bytesUploaded,
59
+ totalBytes: payload.totalBytes
60
+ })
61
+
62
+ return { success: true, progress }
63
+ }
64
+
65
+ async completeUpload(payload: { fileUrl: string }) {
66
+ this.setState({
67
+ status: 'complete',
68
+ progress: 100,
69
+ fileUrl: payload.fileUrl,
70
+ error: null
71
+ })
72
+
73
+ return { success: true }
74
+ }
75
+
76
+ async failUpload(payload: { error: string }) {
77
+ this.setState({
78
+ status: 'error',
79
+ error: payload.error || 'Upload failed'
80
+ })
81
+
82
+ return { success: true }
83
+ }
84
+
85
+ async reset() {
86
+ this.setState({ ...defaultState })
87
+ return { success: true }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Client: useLiveUpload + Widget
93
+
94
+ ```typescript
95
+ // app/client/src/live/UploadDemo.tsx
96
+ import { useLiveUpload } from './useLiveUpload'
97
+ import { LiveUploadWidget } from '../components/LiveUploadWidget'
98
+
99
+ export function UploadDemo() {
100
+ const { live } = useLiveUpload()
101
+
102
+ return (
103
+ <LiveUploadWidget live={live} />
104
+ )
105
+ }
106
+ ```
107
+
108
+ ## Chunked Upload Flow
109
+
110
+ 1. Client calls `startUpload()` (Live Component action).
111
+ 2. Client streams file chunks over WebSocket with `useChunkedUpload`.
112
+ 3. Server assembles file in `uploads/` and returns `/uploads/...`.
113
+ 4. Client maps to `/api/uploads/...` for access.
114
+
115
+ ## Error Handling
116
+
117
+ - If an action throws, the error surfaces in `live.$error` on the client.
118
+ - The widget shows `localError || state.error || $error`.
119
+
120
+ ## Files Involved
121
+
122
+ **Server**
123
+ - `app/server/live/LiveUpload.ts`
124
+ - `core/server/live/FileUploadManager.ts`
125
+ - `core/server/live/websocket-plugin.ts`
126
+
127
+ **Client**
128
+ - `core/client/hooks/useChunkedUpload.ts`
129
+ - `core/client/hooks/useLiveUpload.ts`
130
+ - `app/client/src/components/LiveUploadWidget.tsx`