create-fluxstack 1.9.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 (259) hide show
  1. package/.dockerignore +1 -2
  2. package/Dockerfile +8 -8
  3. package/LIVE_COMPONENTS_REVIEW.md +781 -0
  4. package/LLMD/INDEX.md +64 -0
  5. package/LLMD/MAINTENANCE.md +197 -0
  6. package/LLMD/MIGRATION.md +156 -0
  7. package/LLMD/config/.gitkeep +1 -0
  8. package/LLMD/config/declarative-system.md +268 -0
  9. package/LLMD/config/environment-vars.md +327 -0
  10. package/LLMD/config/runtime-reload.md +401 -0
  11. package/LLMD/core/.gitkeep +1 -0
  12. package/LLMD/core/build-system.md +599 -0
  13. package/LLMD/core/framework-lifecycle.md +229 -0
  14. package/LLMD/core/plugin-system.md +451 -0
  15. package/LLMD/patterns/.gitkeep +1 -0
  16. package/LLMD/patterns/anti-patterns.md +297 -0
  17. package/LLMD/patterns/project-structure.md +264 -0
  18. package/LLMD/patterns/type-safety.md +440 -0
  19. package/LLMD/reference/.gitkeep +1 -0
  20. package/LLMD/reference/cli-commands.md +250 -0
  21. package/LLMD/reference/plugin-hooks.md +357 -0
  22. package/LLMD/reference/routing.md +39 -0
  23. package/LLMD/reference/troubleshooting.md +364 -0
  24. package/LLMD/resources/.gitkeep +1 -0
  25. package/LLMD/resources/controllers.md +465 -0
  26. package/LLMD/resources/live-components.md +703 -0
  27. package/LLMD/resources/live-rooms.md +482 -0
  28. package/LLMD/resources/live-upload.md +130 -0
  29. package/LLMD/resources/plugins-external.md +617 -0
  30. package/LLMD/resources/routes-eden.md +254 -0
  31. package/README.md +37 -17
  32. package/app/client/index.html +0 -1
  33. package/app/client/src/App.tsx +109 -156
  34. package/app/client/src/components/AppLayout.tsx +68 -0
  35. package/app/client/src/components/BackButton.tsx +13 -0
  36. package/app/client/src/components/DemoPage.tsx +20 -0
  37. package/app/client/src/components/LiveUploadWidget.tsx +204 -0
  38. package/app/client/src/lib/eden-api.ts +85 -65
  39. package/app/client/src/live/ChatDemo.tsx +107 -0
  40. package/app/client/src/live/CounterDemo.tsx +206 -0
  41. package/app/client/src/live/FormDemo.tsx +119 -0
  42. package/app/client/src/live/RoomChatDemo.tsx +242 -0
  43. package/app/client/src/live/UploadDemo.tsx +21 -0
  44. package/app/client/src/main.tsx +13 -10
  45. package/app/client/src/pages/ApiTestPage.tsx +108 -0
  46. package/app/client/src/pages/HomePage.tsx +76 -0
  47. package/app/client/src/vite-env.d.ts +1 -1
  48. package/app/server/app.ts +1 -4
  49. package/app/server/controllers/users.controller.ts +36 -44
  50. package/app/server/index.ts +24 -107
  51. package/app/server/live/LiveChat.ts +77 -0
  52. package/app/server/live/LiveCounter.ts +67 -0
  53. package/app/server/live/LiveForm.ts +63 -0
  54. package/app/server/live/LiveLocalCounter.ts +32 -0
  55. package/app/server/live/LiveRoomChat.ts +285 -0
  56. package/app/server/live/LiveUpload.ts +81 -0
  57. package/app/server/live/register-components.ts +19 -19
  58. package/app/server/routes/index.ts +3 -1
  59. package/app/server/routes/room.routes.ts +117 -0
  60. package/app/server/routes/users.routes.ts +35 -27
  61. package/app/shared/types/index.ts +14 -2
  62. package/config/app.config.ts +2 -62
  63. package/config/client.config.ts +2 -95
  64. package/config/database.config.ts +2 -99
  65. package/config/fluxstack.config.ts +25 -45
  66. package/config/index.ts +57 -38
  67. package/config/monitoring.config.ts +2 -114
  68. package/config/plugins.config.ts +2 -80
  69. package/config/server.config.ts +2 -68
  70. package/config/services.config.ts +2 -130
  71. package/config/system/app.config.ts +29 -0
  72. package/config/system/build.config.ts +49 -0
  73. package/config/system/client.config.ts +68 -0
  74. package/config/system/database.config.ts +17 -0
  75. package/config/system/fluxstack.config.ts +114 -0
  76. package/config/{logger.config.ts → system/logger.config.ts} +3 -1
  77. package/config/system/monitoring.config.ts +114 -0
  78. package/config/system/plugins.config.ts +84 -0
  79. package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
  80. package/config/system/server.config.ts +68 -0
  81. package/config/system/services.config.ts +46 -0
  82. package/config/{system.config.ts → system/system.config.ts} +1 -1
  83. package/core/build/bundler.ts +4 -1
  84. package/core/build/flux-plugins-generator.ts +325 -325
  85. package/core/build/index.ts +159 -27
  86. package/core/build/live-components-generator.ts +70 -3
  87. package/core/build/optimizer.ts +235 -235
  88. package/core/cli/command-registry.ts +6 -4
  89. package/core/cli/commands/build.ts +79 -0
  90. package/core/cli/commands/create.ts +54 -0
  91. package/core/cli/commands/dev.ts +101 -0
  92. package/core/cli/commands/help.ts +34 -0
  93. package/core/cli/commands/index.ts +34 -0
  94. package/core/cli/commands/make-plugin.ts +90 -0
  95. package/core/cli/commands/plugin-add.ts +197 -0
  96. package/core/cli/commands/plugin-deps.ts +2 -2
  97. package/core/cli/commands/plugin-list.ts +208 -0
  98. package/core/cli/commands/plugin-remove.ts +170 -0
  99. package/core/cli/generators/component.ts +769 -769
  100. package/core/cli/generators/controller.ts +1 -1
  101. package/core/cli/generators/index.ts +146 -146
  102. package/core/cli/generators/interactive.ts +227 -227
  103. package/core/cli/generators/plugin.ts +2 -2
  104. package/core/cli/generators/prompts.ts +82 -82
  105. package/core/cli/generators/route.ts +6 -6
  106. package/core/cli/generators/service.ts +2 -2
  107. package/core/cli/generators/template-engine.ts +4 -3
  108. package/core/cli/generators/types.ts +2 -2
  109. package/core/cli/generators/utils.ts +191 -191
  110. package/core/cli/index.ts +115 -558
  111. package/core/cli/plugin-discovery.ts +2 -2
  112. package/core/client/LiveComponentsProvider.tsx +63 -17
  113. package/core/client/api/eden.ts +183 -0
  114. package/core/client/api/index.ts +11 -0
  115. package/core/client/components/Live.tsx +104 -0
  116. package/core/client/fluxstack.ts +1 -9
  117. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -0
  118. package/core/client/hooks/state-validator.ts +1 -1
  119. package/core/client/hooks/useAuth.ts +48 -48
  120. package/core/client/hooks/useChunkedUpload.ts +170 -69
  121. package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
  122. package/core/client/hooks/useLiveComponent.ts +800 -0
  123. package/core/client/hooks/useLiveUpload.ts +71 -0
  124. package/core/client/hooks/useRoom.ts +409 -0
  125. package/core/client/hooks/useRoomProxy.ts +382 -0
  126. package/core/client/index.ts +18 -51
  127. package/core/client/standalone-entry.ts +8 -0
  128. package/core/client/standalone.ts +74 -53
  129. package/core/client/state/createStore.ts +192 -192
  130. package/core/client/state/index.ts +14 -14
  131. package/core/config/index.ts +70 -291
  132. package/core/config/schema.ts +42 -723
  133. package/core/framework/client.ts +131 -131
  134. package/core/framework/index.ts +7 -7
  135. package/core/framework/server.ts +227 -47
  136. package/core/framework/types.ts +2 -2
  137. package/core/index.ts +23 -4
  138. package/core/live/ComponentRegistry.ts +7 -3
  139. package/core/live/types.ts +77 -0
  140. package/core/plugins/built-in/index.ts +134 -131
  141. package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1074
  142. package/core/plugins/built-in/live-components/index.ts +1 -1
  143. package/core/plugins/built-in/monitoring/index.ts +111 -47
  144. package/core/plugins/built-in/static/index.ts +1 -1
  145. package/core/plugins/built-in/swagger/index.ts +68 -265
  146. package/core/plugins/built-in/vite/index.ts +94 -306
  147. package/core/plugins/built-in/vite/vite-dev.ts +82 -0
  148. package/core/plugins/config.ts +9 -7
  149. package/core/plugins/dependency-manager.ts +31 -1
  150. package/core/plugins/discovery.ts +19 -7
  151. package/core/plugins/executor.ts +2 -2
  152. package/core/plugins/index.ts +203 -203
  153. package/core/plugins/manager.ts +27 -39
  154. package/core/plugins/module-resolver.ts +19 -8
  155. package/core/plugins/registry.ts +309 -21
  156. package/core/plugins/types.ts +106 -55
  157. package/core/server/framework.ts +66 -43
  158. package/core/server/index.ts +15 -16
  159. package/core/server/live/ComponentRegistry.ts +91 -75
  160. package/core/server/live/FileUploadManager.ts +41 -31
  161. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  162. package/core/server/live/LiveRoomManager.ts +261 -0
  163. package/core/server/live/RoomEventBus.ts +234 -0
  164. package/core/server/live/RoomStateManager.ts +172 -0
  165. package/core/server/live/StateSignature.ts +643 -643
  166. package/core/server/live/WebSocketConnectionManager.ts +30 -19
  167. package/core/server/live/auto-generated-components.ts +41 -26
  168. package/core/server/live/index.ts +14 -0
  169. package/core/server/live/websocket-plugin.ts +233 -72
  170. package/core/server/middleware/elysia-helpers.ts +7 -2
  171. package/core/server/middleware/errorHandling.ts +1 -1
  172. package/core/server/middleware/index.ts +31 -31
  173. package/core/server/plugins/database.ts +180 -180
  174. package/core/server/plugins/static-files-plugin.ts +69 -260
  175. package/core/server/plugins/swagger.ts +33 -33
  176. package/core/server/rooms/RoomBroadcaster.ts +357 -0
  177. package/core/server/rooms/RoomSystem.ts +463 -0
  178. package/core/server/rooms/index.ts +13 -0
  179. package/core/server/services/BaseService.ts +1 -1
  180. package/core/server/services/ServiceContainer.ts +1 -1
  181. package/core/server/services/index.ts +8 -8
  182. package/core/templates/create-project.ts +12 -12
  183. package/core/testing/index.ts +9 -9
  184. package/core/testing/setup.ts +73 -73
  185. package/core/types/api.ts +168 -168
  186. package/core/types/build.ts +219 -218
  187. package/core/types/config.ts +56 -26
  188. package/core/types/index.ts +4 -4
  189. package/core/types/plugin.ts +107 -99
  190. package/core/types/types.ts +490 -14
  191. package/core/utils/build-logger.ts +324 -324
  192. package/core/utils/config-schema.ts +480 -480
  193. package/core/utils/env.ts +2 -8
  194. package/core/utils/errors/codes.ts +114 -114
  195. package/core/utils/errors/handlers.ts +36 -1
  196. package/core/utils/errors/index.ts +49 -5
  197. package/core/utils/errors/middleware.ts +113 -113
  198. package/core/utils/helpers.ts +6 -16
  199. package/core/utils/index.ts +17 -17
  200. package/core/utils/logger/colors.ts +114 -114
  201. package/core/utils/logger/config.ts +13 -9
  202. package/core/utils/logger/formatter.ts +82 -82
  203. package/core/utils/logger/group-logger.ts +101 -101
  204. package/core/utils/logger/index.ts +6 -1
  205. package/core/utils/logger/stack-trace.ts +3 -1
  206. package/core/utils/logger/startup-banner.ts +82 -66
  207. package/core/utils/logger/winston-logger.ts +152 -152
  208. package/core/utils/monitoring/index.ts +211 -211
  209. package/core/utils/sync-version.ts +66 -66
  210. package/core/utils/version.ts +1 -1
  211. package/create-fluxstack.ts +8 -7
  212. package/eslint.config.js +23 -23
  213. package/package.json +14 -15
  214. package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
  215. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  216. package/plugins/crypto-auth/client/components/index.ts +11 -11
  217. package/plugins/crypto-auth/client/index.ts +11 -11
  218. package/plugins/crypto-auth/config/index.ts +1 -1
  219. package/plugins/crypto-auth/index.ts +4 -4
  220. package/plugins/crypto-auth/package.json +65 -65
  221. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  222. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  223. package/plugins/crypto-auth/server/index.ts +21 -21
  224. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
  225. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  226. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
  227. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
  228. package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
  229. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  230. package/plugins/crypto-auth/server/middlewares.ts +19 -19
  231. package/tsconfig.api-strict.json +16 -0
  232. package/tsconfig.json +10 -14
  233. package/{app/client/tsconfig.node.json → tsconfig.node.json} +1 -1
  234. package/types/global.d.ts +29 -29
  235. package/types/vitest.d.ts +8 -8
  236. package/vite.config.ts +38 -62
  237. package/vitest.config.live.ts +10 -9
  238. package/vitest.config.ts +29 -17
  239. package/workspace.json +5 -5
  240. package/app/client/README.md +0 -69
  241. package/app/client/SIMPLIFICATION.md +0 -140
  242. package/app/client/frontend-only.ts +0 -12
  243. package/app/client/tsconfig.app.json +0 -44
  244. package/app/client/tsconfig.json +0 -7
  245. package/app/client/zustand-setup.md +0 -65
  246. package/app/server/backend-only.ts +0 -18
  247. package/app/server/live/LiveClockComponent.ts +0 -215
  248. package/app/server/routes/env-test.ts +0 -110
  249. package/core/client/hooks/index.ts +0 -7
  250. package/core/client/hooks/useHybridLiveComponent.ts +0 -631
  251. package/core/client/hooks/useWebSocket.ts +0 -373
  252. package/core/config/env.ts +0 -546
  253. package/core/config/loader.ts +0 -522
  254. package/core/config/runtime-config.ts +0 -327
  255. package/core/config/validator.ts +0 -540
  256. package/core/server/backend-entry.ts +0 -51
  257. package/core/server/standalone.ts +0 -106
  258. package/core/utils/regenerate-files.ts +0 -69
  259. package/fluxstack.config.ts +0 -354
@@ -0,0 +1,206 @@
1
+ // 🔥 CounterDemo - Contador isolado e compartilhado
2
+
3
+ import { useMemo } from 'react'
4
+ import { Live } from '@/core/client'
5
+ import { LiveCounter } from '@server/live/LiveCounter'
6
+ import { LiveLocalCounter } from '@server/live/LiveLocalCounter'
7
+
8
+ export function CounterDemo() {
9
+ const isolatedRoom = useMemo(() => {
10
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
11
+ return `local-${crypto.randomUUID()}`
12
+ }
13
+ return `local-${Math.random().toString(36).slice(2)}`
14
+ }, [])
15
+
16
+ const sharedCounter = Live.use(LiveCounter, {
17
+ room: 'global-counter',
18
+ initialState: LiveCounter.defaultState
19
+ })
20
+
21
+ const isolatedCounter = Live.use(LiveCounter, {
22
+ room: isolatedRoom,
23
+ initialState: LiveCounter.defaultState,
24
+ persistState: false
25
+ })
26
+
27
+ const localCounter = Live.use(LiveLocalCounter, {
28
+ initialState: LiveLocalCounter.defaultState,
29
+ persistState: false
30
+ })
31
+
32
+ const renderCounter = (
33
+ title: string,
34
+ description: string,
35
+ counter: ReturnType<typeof Live.use>
36
+ ) => {
37
+ const handleIncrement = async () => {
38
+ await counter.increment()
39
+ }
40
+
41
+ const handleDecrement = async () => {
42
+ await counter.decrement()
43
+ }
44
+
45
+ const handleReset = async () => {
46
+ await counter.reset()
47
+ }
48
+
49
+ return (
50
+ <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
51
+ <h2 className="text-2xl font-bold text-white mb-2 text-center">
52
+ {title}
53
+ </h2>
54
+
55
+ <p className="text-gray-400 text-sm text-center mb-6">
56
+ {description}
57
+ </p>
58
+
59
+ <div className="flex justify-center gap-4 mb-6">
60
+ <div className={`flex items-center gap-2 px-3 py-1 rounded-full text-xs ${
61
+ counter.$connected
62
+ ? 'bg-emerald-500/20 text-emerald-300'
63
+ : 'bg-red-500/20 text-red-300'
64
+ }`}>
65
+ <div className={`w-2 h-2 rounded-full ${
66
+ counter.$connected ? 'bg-emerald-400' : 'bg-red-400'
67
+ }`} />
68
+ {counter.$connected ? 'Conectado' : 'Desconectado'}
69
+ </div>
70
+
71
+ <div className="flex items-center gap-2 px-3 py-1 rounded-full text-xs bg-blue-500/20 text-blue-300">
72
+ <span>👥</span>
73
+ {counter.$state.connectedUsers} usuário(s)
74
+ </div>
75
+ </div>
76
+
77
+ <div className="text-center mb-8">
78
+ <div className="text-8xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
79
+ {counter.$state.count}
80
+ </div>
81
+
82
+ {counter.$state.lastUpdatedBy && (
83
+ <p className="text-gray-500 text-sm mt-2">
84
+ Última atualização: {counter.$state.lastUpdatedBy}
85
+ </p>
86
+ )}
87
+ </div>
88
+
89
+ <div className="flex gap-4 justify-center">
90
+ <button
91
+ onClick={handleDecrement}
92
+ disabled={counter.$loading}
93
+ className="w-14 h-14 flex items-center justify-center text-3xl bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-300 rounded-xl transition-all disabled:opacity-50"
94
+ >
95
+
96
+ </button>
97
+
98
+ <button
99
+ onClick={handleReset}
100
+ disabled={counter.$loading}
101
+ className="px-6 h-14 flex items-center justify-center text-sm bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 text-gray-300 rounded-xl transition-all disabled:opacity-50"
102
+ >
103
+ Reset
104
+ </button>
105
+
106
+ <button
107
+ onClick={handleIncrement}
108
+ disabled={counter.$loading}
109
+ className="w-14 h-14 flex items-center justify-center text-3xl bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 text-emerald-300 rounded-xl transition-all disabled:opacity-50"
110
+ >
111
+ +
112
+ </button>
113
+ </div>
114
+
115
+ {counter.$loading && (
116
+ <div className="flex justify-center mt-4">
117
+ <div className="w-5 h-5 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
118
+ </div>
119
+ )}
120
+
121
+ <div className="mt-8 pt-6 border-t border-white/10">
122
+ <p className="text-gray-500 text-xs text-center">
123
+ ✨ Usando <code className="text-purple-400">Room Events</code>
124
+ </p>
125
+ </div>
126
+ </div>
127
+ )
128
+ }
129
+
130
+ const renderLocalCounter = () => {
131
+ const handleIncrement = async () => {
132
+ await localCounter.increment()
133
+ }
134
+ const handleDecrement = async () => {
135
+ await localCounter.decrement()
136
+ }
137
+ const handleReset = async () => {
138
+ await localCounter.reset()
139
+ }
140
+
141
+ return (
142
+ <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-8 max-w-md w-full">
143
+ <h2 className="text-2xl font-bold text-white mb-2 text-center">
144
+ Contador Local (sem Room)
145
+ </h2>
146
+ <p className="text-gray-400 text-sm text-center mb-6">
147
+ Estado local do componente, sem eventos de sala.
148
+ </p>
149
+
150
+ <div className="text-center mb-8">
151
+ <div className="text-8xl font-bold bg-gradient-to-r from-amber-400 via-orange-400 to-rose-400 bg-clip-text text-transparent">
152
+ {localCounter.$state.count}
153
+ </div>
154
+ </div>
155
+
156
+ <div className="flex gap-4 justify-center">
157
+ <button
158
+ onClick={handleDecrement}
159
+ disabled={localCounter.$loading}
160
+ className="w-14 h-14 flex items-center justify-center text-3xl bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 text-red-300 rounded-xl transition-all disabled:opacity-50"
161
+ >
162
+
163
+ </button>
164
+
165
+ <button
166
+ onClick={handleReset}
167
+ disabled={localCounter.$loading}
168
+ className="px-6 h-14 flex items-center justify-center text-sm bg-gray-500/20 hover:bg-gray-500/30 border border-gray-500/30 text-gray-300 rounded-xl transition-all disabled:opacity-50"
169
+ >
170
+ Reset
171
+ </button>
172
+
173
+ <button
174
+ onClick={handleIncrement}
175
+ disabled={localCounter.$loading}
176
+ className="w-14 h-14 flex items-center justify-center text-3xl bg-emerald-500/20 hover:bg-emerald-500/30 border border-emerald-500/30 text-emerald-300 rounded-xl transition-all disabled:opacity-50"
177
+ >
178
+ +
179
+ </button>
180
+ </div>
181
+
182
+ {localCounter.$loading && (
183
+ <div className="flex justify-center mt-4">
184
+ <div className="w-5 h-5 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
185
+ </div>
186
+ )}
187
+ </div>
188
+ )
189
+ }
190
+
191
+ return (
192
+ <div className="flex flex-col lg:flex-row gap-6 items-start justify-center">
193
+ {renderLocalCounter()}
194
+ {renderCounter(
195
+ 'Contador Isolado',
196
+ 'Cada aba tem seu próprio valor (room único).',
197
+ isolatedCounter
198
+ )}
199
+ {renderCounter(
200
+ 'Contador Compartilhado',
201
+ 'Abra em várias abas - todos veem o mesmo valor!',
202
+ sharedCounter
203
+ )}
204
+ </div>
205
+ )
206
+ }
@@ -0,0 +1,119 @@
1
+ // 🔥 FormDemo - Exemplo de Live Component
2
+ import { Live } from '@/core/client'
3
+ import { LiveForm } from '@server/live/LiveForm'
4
+
5
+ export function FormDemo() {
6
+ // ✨ Usa defaultState do backend automaticamente
7
+ const form = Live.use(LiveForm)
8
+
9
+ // Sucesso
10
+ if (form.submitted) {
11
+ return (
12
+ <div className="p-6 bg-green-500/20 border border-green-500/30 rounded-xl text-center">
13
+ <div className="text-4xl mb-3">✅</div>
14
+ <h2 className="text-xl font-bold text-white mb-2">Enviado!</h2>
15
+ <p className="text-gray-300">Obrigado, <span className="text-green-400">{form.name}</span>!</p>
16
+ <p className="text-gray-400 text-sm mt-2">
17
+ Enviado em: {form.submittedAt ? new Date(form.submittedAt).toLocaleString() : '-'}
18
+ </p>
19
+ <button
20
+ onClick={() => form.reset()}
21
+ className="mt-4 px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-all"
22
+ >
23
+ Novo Formulário
24
+ </button>
25
+ </div>
26
+ )
27
+ }
28
+
29
+ return (
30
+ <div className="p-6 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl">
31
+ <div className="flex items-center justify-between mb-6">
32
+ <h2 className="text-xl font-bold text-white">Live Form</h2>
33
+ <span className={`px-3 py-1 rounded-full text-xs ${
34
+ form.$connected ? 'bg-green-500/20 text-green-300' : 'bg-red-500/20 text-red-300'
35
+ }`}>
36
+ {form.$connected ? '🟢 Conectado' : '🔴 Desconectado'}
37
+ </span>
38
+ </div>
39
+
40
+ <div className="space-y-4">
41
+ {/* Nome - sync on blur */}
42
+ <div>
43
+ <label className="block text-gray-300 text-sm mb-1">
44
+ Nome <span className="text-purple-400 text-xs">(sync: blur)</span>
45
+ </label>
46
+ <input
47
+ {...form.$field('name', { syncOn: 'blur' })}
48
+ placeholder="Seu nome"
49
+ className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
50
+ />
51
+ </div>
52
+
53
+ {/* Email - sync on change with debounce */}
54
+ <div>
55
+ <label className="block text-gray-300 text-sm mb-1">
56
+ Email <span className="text-blue-400 text-xs">(sync: 500ms)</span>
57
+ </label>
58
+ <input
59
+ {...form.$field('email', { syncOn: 'change', debounce: 500 })}
60
+ type="email"
61
+ placeholder="seu@email.com"
62
+ className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none"
63
+ />
64
+ </div>
65
+
66
+ {/* Mensagem - sync on blur */}
67
+ <div>
68
+ <label className="block text-gray-300 text-sm mb-1">
69
+ Mensagem <span className="text-orange-400 text-xs">(sync: blur)</span>
70
+ </label>
71
+ <textarea
72
+ {...form.$field('message', { syncOn: 'blur' })}
73
+ rows={3}
74
+ placeholder="Sua mensagem..."
75
+ className="w-full px-4 py-3 bg-white/5 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-purple-500 focus:outline-none resize-none"
76
+ />
77
+ </div>
78
+
79
+ {/* Botões */}
80
+ <div className="flex gap-2">
81
+ <button
82
+ onClick={async () => {
83
+ try {
84
+ await form.$sync()
85
+ await form.submit()
86
+ } catch (err: any) {
87
+ alert(err.message || 'Erro ao enviar')
88
+ }
89
+ }}
90
+ disabled={!form.$connected}
91
+ className="flex-1 px-4 py-3 bg-gradient-to-r from-purple-500 to-pink-500 text-white rounded-lg font-medium hover:shadow-lg hover:shadow-purple-500/30 transition-all disabled:opacity-50"
92
+ >
93
+ {form.$loading ? 'Enviando...' : 'Enviar'}
94
+ </button>
95
+ <button
96
+ onClick={() => form.reset()}
97
+ className="px-4 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-500 transition-all"
98
+ >
99
+ Limpar
100
+ </button>
101
+ </div>
102
+ </div>
103
+
104
+ {/* Legenda */}
105
+ <div className="mt-4 p-3 bg-white/5 rounded-lg text-xs text-gray-400 space-y-1">
106
+ <p><span className="text-purple-400">blur:</span> Sincroniza ao sair do campo</p>
107
+ <p><span className="text-blue-400">500ms:</span> Sincroniza 500ms após parar de digitar</p>
108
+ </div>
109
+
110
+ {/* Debug */}
111
+ <details className="mt-4">
112
+ <summary className="text-gray-400 text-sm cursor-pointer">Debug State (servidor)</summary>
113
+ <pre className="mt-2 p-3 bg-black/40 rounded-lg text-xs text-green-400 overflow-auto">
114
+ {JSON.stringify(form.$state, null, 2)}
115
+ </pre>
116
+ </details>
117
+ </div>
118
+ )
119
+ }
@@ -0,0 +1,242 @@
1
+ // 🔥 RoomChatDemo - Demo do chat com múltiplas salas
2
+ //
3
+ // Demonstra o uso do sistema $room para:
4
+ // - Entrar/sair de múltiplas salas
5
+ // - Enviar mensagens para sala ativa
6
+ // - Ver quem está digitando
7
+ // - Trocar entre salas
8
+
9
+ import { useState, useEffect, useRef, useMemo } from 'react'
10
+ import { Live } from '@/core/client'
11
+ import { LiveRoomChat } from '@server/live/LiveRoomChat'
12
+
13
+ // Salas disponíveis
14
+ const AVAILABLE_ROOMS = [
15
+ { id: 'geral', name: '💬 Geral' },
16
+ { id: 'tech', name: '💻 Tecnologia' },
17
+ { id: 'random', name: '🎲 Random' },
18
+ { id: 'vip', name: '⭐ VIP' }
19
+ ]
20
+
21
+ export function RoomChatDemo() {
22
+ const [text, setText] = useState('')
23
+ const messagesEndRef = useRef<HTMLDivElement>(null)
24
+
25
+ // Username aleatório
26
+ const defaultUsername = useMemo(() => {
27
+ const adjectives = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave']
28
+ const nouns = ['Panda', 'Tiger', 'Eagle', 'Wolf', 'Bear']
29
+ const adj = adjectives[Math.floor(Math.random() * adjectives.length)]
30
+ const noun = nouns[Math.floor(Math.random() * nouns.length)]
31
+ const num = Math.floor(Math.random() * 100)
32
+ return `${adj}${noun}${num}`
33
+ }, [])
34
+
35
+ // Live component - estado sincronizado automaticamente
36
+ const chat = Live.use(LiveRoomChat, {
37
+ initialState: { ...LiveRoomChat.defaultState, username: defaultUsername }
38
+ })
39
+
40
+ // Mensagens e typing vêm diretamente do estado sincronizado
41
+ const activeRoom = chat.$state.activeRoom
42
+ const activeMessages = activeRoom ? (chat.$state.messages[activeRoom] || []) : []
43
+ const activeTyping = activeRoom ? (chat.$state.typingUsers[activeRoom] || []) : []
44
+
45
+ // Auto scroll quando mensagens mudam
46
+ useEffect(() => {
47
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
48
+ }, [activeMessages.length])
49
+
50
+ // Handlers
51
+ const handleJoinRoom = async (roomId: string, roomName: string) => {
52
+ if (chat.$rooms.includes(roomId)) {
53
+ await chat.switchRoom({ roomId })
54
+ } else {
55
+ await chat.joinRoom({ roomId, roomName })
56
+ }
57
+ }
58
+
59
+ const handleLeaveRoom = async (roomId: string) => {
60
+ await chat.leaveRoom({ roomId })
61
+ }
62
+
63
+ const handleSendMessage = async () => {
64
+ if (!text.trim() || !chat.$state.activeRoom) return
65
+ await chat.sendMessage({ text })
66
+ setText('')
67
+ }
68
+
69
+ const handleTyping = () => {
70
+ chat.startTyping({})
71
+ }
72
+
73
+ return (
74
+ <div className="flex h-[600px] bg-gray-900 rounded-2xl overflow-hidden border border-white/10">
75
+ {/* Sidebar - Lista de Salas */}
76
+ <div className="w-64 bg-gray-800/50 border-r border-white/10 flex flex-col">
77
+ {/* Header */}
78
+ <div className="p-4 border-b border-white/10">
79
+ <h2 className="text-lg font-bold text-white mb-2">💬 Room Chat</h2>
80
+ <div className="flex items-center gap-2">
81
+ <div className={`w-2 h-2 rounded-full ${chat.$connected ? 'bg-emerald-400' : 'bg-red-400'}`} />
82
+ <span className="text-sm text-gray-400">{chat.$state.username}</span>
83
+ </div>
84
+ </div>
85
+
86
+ {/* Salas Disponíveis */}
87
+ <div className="flex-1 overflow-auto p-2">
88
+ <p className="text-xs text-gray-500 px-2 py-1">SALAS</p>
89
+ {AVAILABLE_ROOMS.map(room => {
90
+ const isJoined = chat.$rooms.includes(room.id)
91
+ const isActive = chat.$state.activeRoom === room.id
92
+ const unreadCount = 0 // TODO: implementar contagem
93
+
94
+ return (
95
+ <div
96
+ key={room.id}
97
+ onClick={() => handleJoinRoom(room.id, room.name)}
98
+ className={`
99
+ flex items-center justify-between px-3 py-2 rounded-lg cursor-pointer mb-1
100
+ transition-all group
101
+ ${isActive
102
+ ? 'bg-purple-500/20 text-purple-300'
103
+ : isJoined
104
+ ? 'bg-white/5 text-gray-300 hover:bg-white/10'
105
+ : 'text-gray-500 hover:bg-white/5 hover:text-gray-400'
106
+ }
107
+ `}
108
+ >
109
+ <span className="flex items-center gap-2">
110
+ {room.name}
111
+ {isJoined && (
112
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
113
+ )}
114
+ </span>
115
+
116
+ {isJoined && !isActive && (
117
+ <button
118
+ onClick={(e) => {
119
+ e.stopPropagation()
120
+ handleLeaveRoom(room.id)
121
+ }}
122
+ className="opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-300 text-xs"
123
+ >
124
+
125
+ </button>
126
+ )}
127
+ </div>
128
+ )
129
+ })}
130
+ </div>
131
+
132
+ {/* Salas ativas */}
133
+ <div className="p-3 border-t border-white/10">
134
+ <p className="text-xs text-gray-500 mb-1">
135
+ Em {chat.$rooms.length} sala(s)
136
+ </p>
137
+ </div>
138
+ </div>
139
+
140
+ {/* Chat Area */}
141
+ <div className="flex-1 flex flex-col">
142
+ {activeRoom ? (
143
+ <>
144
+ {/* Header da sala */}
145
+ <div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
146
+ <div>
147
+ <h3 className="text-white font-semibold">
148
+ {chat.$state.rooms.find(r => r.id === activeRoom)?.name || activeRoom}
149
+ </h3>
150
+ <p className="text-xs text-gray-500">
151
+ {activeMessages.length} mensagens
152
+ </p>
153
+ </div>
154
+ <button
155
+ onClick={() => handleLeaveRoom(activeRoom)}
156
+ className="px-3 py-1 text-sm bg-red-500/20 text-red-300 rounded-lg hover:bg-red-500/30 transition-all"
157
+ >
158
+ Sair
159
+ </button>
160
+ </div>
161
+
162
+ {/* Mensagens */}
163
+ <div className="flex-1 overflow-auto p-4 space-y-3">
164
+ {activeMessages.length === 0 ? (
165
+ <div className="text-center text-gray-500 py-8">
166
+ <p>Nenhuma mensagem ainda</p>
167
+ <p className="text-sm">Seja o primeiro a enviar!</p>
168
+ </div>
169
+ ) : (
170
+ activeMessages.map(msg => (
171
+ <div
172
+ key={msg.id}
173
+ className={`flex flex-col ${msg.user === chat.$state.username ? 'items-end' : 'items-start'}`}
174
+ >
175
+ <div
176
+ className={`
177
+ max-w-[80%] rounded-2xl px-4 py-2
178
+ ${msg.user === chat.$state.username
179
+ ? 'bg-purple-500/30 text-purple-100'
180
+ : 'bg-white/10 text-gray-200'
181
+ }
182
+ `}
183
+ >
184
+ <p className="text-xs text-gray-400 mb-1">{msg.user}</p>
185
+ <p>{msg.text}</p>
186
+ </div>
187
+ <span className="text-xs text-gray-600 mt-1">
188
+ {new Date(msg.timestamp).toLocaleTimeString()}
189
+ </span>
190
+ </div>
191
+ ))
192
+ )}
193
+ <div ref={messagesEndRef} />
194
+ </div>
195
+
196
+ {/* Typing indicator */}
197
+ {activeTyping.length > 0 && (
198
+ <div className="px-4 py-1 text-xs text-gray-500">
199
+ {activeTyping.filter(u => u !== chat.$state.username).join(', ')} está digitando...
200
+ </div>
201
+ )}
202
+
203
+ {/* Input */}
204
+ <div className="p-4 border-t border-white/10">
205
+ <div className="flex gap-2">
206
+ <input
207
+ value={text}
208
+ onChange={(e) => {
209
+ setText(e.target.value)
210
+ handleTyping()
211
+ }}
212
+ onKeyDown={(e) => {
213
+ if (e.key === 'Enter' && !e.shiftKey) {
214
+ e.preventDefault()
215
+ handleSendMessage()
216
+ }
217
+ }}
218
+ placeholder="Digite uma mensagem..."
219
+ className="flex-1 px-4 py-2 rounded-xl bg-white/10 border border-white/20 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-purple-500/50"
220
+ />
221
+ <button
222
+ onClick={handleSendMessage}
223
+ disabled={!text.trim()}
224
+ className="px-6 py-2 rounded-xl bg-purple-500/30 text-purple-200 hover:bg-purple-500/40 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
225
+ >
226
+ Enviar
227
+ </button>
228
+ </div>
229
+ </div>
230
+ </>
231
+ ) : (
232
+ <div className="flex-1 flex items-center justify-center text-gray-500">
233
+ <div className="text-center">
234
+ <p className="text-4xl mb-4">👈</p>
235
+ <p>Selecione uma sala para começar</p>
236
+ </div>
237
+ </div>
238
+ )}
239
+ </div>
240
+ </div>
241
+ )
242
+ }
@@ -0,0 +1,21 @@
1
+ import { useState } from 'react'
2
+ import { LiveUploadWidget } from '../components/LiveUploadWidget'
3
+ import { useLiveUpload } from '@/core/client'
4
+
5
+ export function UploadDemo() {
6
+ const [lastUrl, setLastUrl] = useState<string | null>(null)
7
+ const { live } = useLiveUpload({
8
+ onComplete: (response) => setLastUrl(response.fileUrl || null)
9
+ })
10
+
11
+ return (
12
+ <div className="flex flex-col gap-4 max-w-xl w-full mx-auto">
13
+ <LiveUploadWidget live={live} />
14
+ {lastUrl && (
15
+ <div className="text-xs text-gray-500 text-center">
16
+ Ultimo arquivo: {lastUrl}
17
+ </div>
18
+ )}
19
+ </div>
20
+ )
21
+ }
@@ -1,10 +1,13 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import './index.css'
4
- import App from './App.tsx'
5
-
6
- createRoot(document.getElementById('root')!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>,
10
- )
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router'
4
+ import './index.css'
5
+ import App from './App.tsx'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>,
13
+ )