create-fluxstack 1.10.1 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/.dockerignore +1 -2
  2. package/Dockerfile +8 -8
  3. package/LLMD/INDEX.md +64 -0
  4. package/LLMD/MAINTENANCE.md +197 -0
  5. package/LLMD/MIGRATION.md +156 -0
  6. package/LLMD/config/.gitkeep +1 -0
  7. package/LLMD/config/declarative-system.md +268 -0
  8. package/LLMD/config/environment-vars.md +327 -0
  9. package/LLMD/config/runtime-reload.md +401 -0
  10. package/LLMD/core/.gitkeep +1 -0
  11. package/LLMD/core/build-system.md +599 -0
  12. package/LLMD/core/framework-lifecycle.md +229 -0
  13. package/LLMD/core/plugin-system.md +451 -0
  14. package/LLMD/patterns/.gitkeep +1 -0
  15. package/LLMD/patterns/anti-patterns.md +297 -0
  16. package/LLMD/patterns/project-structure.md +264 -0
  17. package/LLMD/patterns/type-safety.md +440 -0
  18. package/LLMD/reference/.gitkeep +1 -0
  19. package/LLMD/reference/cli-commands.md +250 -0
  20. package/LLMD/reference/plugin-hooks.md +357 -0
  21. package/LLMD/reference/routing.md +39 -0
  22. package/LLMD/reference/troubleshooting.md +364 -0
  23. package/LLMD/resources/.gitkeep +1 -0
  24. package/LLMD/resources/controllers.md +465 -0
  25. package/LLMD/resources/live-components.md +703 -0
  26. package/LLMD/resources/live-rooms.md +482 -0
  27. package/LLMD/resources/live-upload.md +130 -0
  28. package/LLMD/resources/plugins-external.md +617 -0
  29. package/LLMD/resources/routes-eden.md +254 -0
  30. package/README.md +37 -17
  31. package/app/client/index.html +0 -1
  32. package/app/client/src/App.tsx +107 -150
  33. package/app/client/src/components/AppLayout.tsx +68 -0
  34. package/app/client/src/components/BackButton.tsx +13 -0
  35. package/app/client/src/components/DemoPage.tsx +20 -0
  36. package/app/client/src/components/LiveUploadWidget.tsx +204 -0
  37. package/app/client/src/lib/eden-api.ts +85 -60
  38. package/app/client/src/live/ChatDemo.tsx +107 -0
  39. package/app/client/src/live/CounterDemo.tsx +206 -0
  40. package/app/client/src/live/FormDemo.tsx +119 -0
  41. package/app/client/src/live/RoomChatDemo.tsx +161 -0
  42. package/app/client/src/live/UploadDemo.tsx +21 -0
  43. package/app/client/src/main.tsx +4 -1
  44. package/app/client/src/pages/ApiTestPage.tsx +108 -0
  45. package/app/client/src/pages/HomePage.tsx +76 -0
  46. package/app/server/app.ts +1 -4
  47. package/app/server/controllers/users.controller.ts +36 -44
  48. package/app/server/index.ts +25 -35
  49. package/app/server/live/LiveChat.ts +77 -0
  50. package/app/server/live/LiveCounter.ts +67 -0
  51. package/app/server/live/LiveForm.ts +63 -0
  52. package/app/server/live/LiveLocalCounter.ts +32 -0
  53. package/app/server/live/LiveRoomChat.ts +127 -0
  54. package/app/server/live/LiveUpload.ts +81 -0
  55. package/app/server/routes/index.ts +3 -1
  56. package/app/server/routes/room.routes.ts +117 -0
  57. package/app/server/routes/users.routes.ts +35 -27
  58. package/app/shared/types/index.ts +14 -2
  59. package/config/app.config.ts +2 -62
  60. package/config/client.config.ts +2 -95
  61. package/config/database.config.ts +2 -99
  62. package/config/fluxstack.config.ts +25 -45
  63. package/config/index.ts +57 -38
  64. package/config/monitoring.config.ts +2 -114
  65. package/config/plugins.config.ts +2 -80
  66. package/config/server.config.ts +2 -68
  67. package/config/services.config.ts +2 -130
  68. package/config/system/app.config.ts +29 -0
  69. package/config/system/build.config.ts +49 -0
  70. package/config/system/client.config.ts +68 -0
  71. package/config/system/database.config.ts +17 -0
  72. package/config/system/fluxstack.config.ts +114 -0
  73. package/config/{logger.config.ts → system/logger.config.ts} +3 -1
  74. package/config/system/monitoring.config.ts +114 -0
  75. package/config/system/plugins.config.ts +84 -0
  76. package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
  77. package/config/system/server.config.ts +68 -0
  78. package/config/system/services.config.ts +46 -0
  79. package/config/{system.config.ts → system/system.config.ts} +1 -1
  80. package/core/build/flux-plugins-generator.ts +325 -325
  81. package/core/build/index.ts +39 -27
  82. package/core/build/live-components-generator.ts +3 -3
  83. package/core/build/optimizer.ts +235 -235
  84. package/core/cli/command-registry.ts +6 -4
  85. package/core/cli/commands/build.ts +79 -0
  86. package/core/cli/commands/create.ts +54 -0
  87. package/core/cli/commands/dev.ts +101 -0
  88. package/core/cli/commands/help.ts +34 -0
  89. package/core/cli/commands/index.ts +34 -0
  90. package/core/cli/commands/make-plugin.ts +90 -0
  91. package/core/cli/commands/plugin-add.ts +197 -0
  92. package/core/cli/commands/plugin-deps.ts +2 -2
  93. package/core/cli/commands/plugin-list.ts +208 -0
  94. package/core/cli/commands/plugin-remove.ts +170 -0
  95. package/core/cli/generators/component.ts +769 -769
  96. package/core/cli/generators/controller.ts +1 -1
  97. package/core/cli/generators/index.ts +146 -146
  98. package/core/cli/generators/interactive.ts +227 -227
  99. package/core/cli/generators/plugin.ts +2 -2
  100. package/core/cli/generators/prompts.ts +82 -82
  101. package/core/cli/generators/route.ts +6 -6
  102. package/core/cli/generators/service.ts +2 -2
  103. package/core/cli/generators/template-engine.ts +4 -3
  104. package/core/cli/generators/types.ts +2 -2
  105. package/core/cli/generators/utils.ts +191 -191
  106. package/core/cli/index.ts +115 -686
  107. package/core/cli/plugin-discovery.ts +2 -2
  108. package/core/client/LiveComponentsProvider.tsx +60 -8
  109. package/core/client/api/eden.ts +183 -0
  110. package/core/client/api/index.ts +11 -0
  111. package/core/client/components/Live.tsx +104 -0
  112. package/core/client/fluxstack.ts +1 -9
  113. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  114. package/core/client/hooks/state-validator.ts +1 -1
  115. package/core/client/hooks/useAuth.ts +48 -48
  116. package/core/client/hooks/useChunkedUpload.ts +85 -35
  117. package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
  118. package/core/client/hooks/useLiveComponent.ts +800 -0
  119. package/core/client/hooks/useLiveUpload.ts +71 -0
  120. package/core/client/hooks/useRoom.ts +409 -0
  121. package/core/client/hooks/useRoomProxy.ts +382 -0
  122. package/core/client/index.ts +17 -68
  123. package/core/client/standalone-entry.ts +8 -0
  124. package/core/client/standalone.ts +74 -53
  125. package/core/client/state/createStore.ts +192 -192
  126. package/core/client/state/index.ts +14 -14
  127. package/core/config/index.ts +70 -291
  128. package/core/config/schema.ts +42 -723
  129. package/core/framework/client.ts +131 -131
  130. package/core/framework/index.ts +7 -7
  131. package/core/framework/server.ts +47 -40
  132. package/core/framework/types.ts +2 -2
  133. package/core/index.ts +23 -4
  134. package/core/live/ComponentRegistry.ts +3 -3
  135. package/core/live/types.ts +77 -0
  136. package/core/plugins/built-in/index.ts +134 -134
  137. package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
  138. package/core/plugins/built-in/live-components/index.ts +1 -1
  139. package/core/plugins/built-in/monitoring/index.ts +111 -47
  140. package/core/plugins/built-in/static/index.ts +1 -1
  141. package/core/plugins/built-in/swagger/index.ts +68 -265
  142. package/core/plugins/built-in/vite/index.ts +85 -185
  143. package/core/plugins/built-in/vite/vite-dev.ts +10 -16
  144. package/core/plugins/config.ts +9 -7
  145. package/core/plugins/dependency-manager.ts +31 -1
  146. package/core/plugins/discovery.ts +19 -7
  147. package/core/plugins/executor.ts +2 -2
  148. package/core/plugins/index.ts +203 -203
  149. package/core/plugins/manager.ts +27 -39
  150. package/core/plugins/module-resolver.ts +19 -8
  151. package/core/plugins/registry.ts +255 -19
  152. package/core/plugins/types.ts +20 -53
  153. package/core/server/framework.ts +66 -43
  154. package/core/server/index.ts +15 -15
  155. package/core/server/live/ComponentRegistry.ts +78 -71
  156. package/core/server/live/FileUploadManager.ts +23 -10
  157. package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
  158. package/core/server/live/LiveRoomManager.ts +261 -0
  159. package/core/server/live/RoomEventBus.ts +234 -0
  160. package/core/server/live/RoomStateManager.ts +172 -0
  161. package/core/server/live/StateSignature.ts +643 -643
  162. package/core/server/live/WebSocketConnectionManager.ts +30 -19
  163. package/core/server/live/auto-generated-components.ts +21 -9
  164. package/core/server/live/index.ts +14 -0
  165. package/core/server/live/websocket-plugin.ts +214 -67
  166. package/core/server/middleware/elysia-helpers.ts +7 -2
  167. package/core/server/middleware/errorHandling.ts +1 -1
  168. package/core/server/middleware/index.ts +31 -31
  169. package/core/server/plugins/database.ts +180 -180
  170. package/core/server/plugins/static-files-plugin.ts +69 -69
  171. package/core/server/plugins/swagger.ts +1 -1
  172. package/core/server/rooms/RoomBroadcaster.ts +357 -0
  173. package/core/server/rooms/RoomSystem.ts +463 -0
  174. package/core/server/rooms/index.ts +13 -0
  175. package/core/server/services/BaseService.ts +1 -1
  176. package/core/server/services/ServiceContainer.ts +1 -1
  177. package/core/server/services/index.ts +8 -8
  178. package/core/templates/create-project.ts +12 -12
  179. package/core/testing/index.ts +9 -9
  180. package/core/testing/setup.ts +73 -73
  181. package/core/types/api.ts +168 -168
  182. package/core/types/build.ts +219 -219
  183. package/core/types/config.ts +56 -26
  184. package/core/types/index.ts +4 -4
  185. package/core/types/plugin.ts +107 -107
  186. package/core/types/types.ts +353 -14
  187. package/core/utils/build-logger.ts +324 -324
  188. package/core/utils/config-schema.ts +480 -480
  189. package/core/utils/env.ts +2 -8
  190. package/core/utils/errors/codes.ts +114 -114
  191. package/core/utils/errors/handlers.ts +36 -1
  192. package/core/utils/errors/index.ts +49 -5
  193. package/core/utils/errors/middleware.ts +113 -113
  194. package/core/utils/helpers.ts +6 -16
  195. package/core/utils/index.ts +17 -17
  196. package/core/utils/logger/colors.ts +114 -114
  197. package/core/utils/logger/config.ts +13 -9
  198. package/core/utils/logger/formatter.ts +82 -82
  199. package/core/utils/logger/group-logger.ts +101 -101
  200. package/core/utils/logger/index.ts +6 -1
  201. package/core/utils/logger/stack-trace.ts +3 -1
  202. package/core/utils/logger/startup-banner.ts +82 -82
  203. package/core/utils/logger/winston-logger.ts +152 -152
  204. package/core/utils/monitoring/index.ts +211 -211
  205. package/core/utils/sync-version.ts +66 -66
  206. package/core/utils/version.ts +1 -1
  207. package/create-fluxstack.ts +8 -7
  208. package/package.json +12 -13
  209. package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
  210. package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
  211. package/plugins/crypto-auth/client/components/index.ts +11 -11
  212. package/plugins/crypto-auth/client/index.ts +11 -11
  213. package/plugins/crypto-auth/config/index.ts +1 -1
  214. package/plugins/crypto-auth/index.ts +4 -4
  215. package/plugins/crypto-auth/package.json +65 -65
  216. package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
  217. package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
  218. package/plugins/crypto-auth/server/index.ts +21 -21
  219. package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
  220. package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
  221. package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
  222. package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
  223. package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
  224. package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
  225. package/tsconfig.api-strict.json +16 -0
  226. package/tsconfig.json +48 -52
  227. package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
  228. package/types/global.d.ts +29 -29
  229. package/types/vitest.d.ts +8 -8
  230. package/vite.config.ts +38 -62
  231. package/vitest.config.live.ts +10 -9
  232. package/vitest.config.ts +29 -17
  233. package/app/client/README.md +0 -69
  234. package/app/client/SIMPLIFICATION.md +0 -140
  235. package/app/client/frontend-only.ts +0 -12
  236. package/app/client/src/live/FileUploadExample.tsx +0 -359
  237. package/app/client/src/live/MinimalLiveClock.tsx +0 -47
  238. package/app/client/src/live/QuickUploadTest.tsx +0 -193
  239. package/app/client/tsconfig.app.json +0 -45
  240. package/app/client/tsconfig.json +0 -7
  241. package/app/client/zustand-setup.md +0 -65
  242. package/app/server/backend-only.ts +0 -18
  243. package/app/server/live/LiveClockComponent.ts +0 -215
  244. package/app/server/live/LiveFileUploadComponent.ts +0 -77
  245. package/app/server/routes/env-test.ts +0 -110
  246. package/core/client/hooks/index.ts +0 -7
  247. package/core/client/hooks/useHybridLiveComponent.ts +0 -685
  248. package/core/client/hooks/useTypedLiveComponent.ts +0 -133
  249. package/core/client/hooks/useWebSocket.ts +0 -361
  250. package/core/config/env.ts +0 -546
  251. package/core/config/loader.ts +0 -522
  252. package/core/config/runtime-config.ts +0 -327
  253. package/core/config/validator.ts +0 -540
  254. package/core/server/backend-entry.ts +0 -51
  255. package/core/server/standalone.ts +0 -106
  256. package/core/utils/regenerate-files.ts +0 -69
  257. package/fluxstack.config.ts +0 -354
@@ -0,0 +1,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,161 @@
1
+ // 🔥 RoomChatDemo - Chat multi-salas simplificado
2
+
3
+ import { useState, useEffect, useRef, useMemo } from 'react'
4
+ import { Live } from '@/core/client'
5
+ import { LiveRoomChat } from '@server/live/LiveRoomChat'
6
+
7
+ const AVAILABLE_ROOMS = [
8
+ { id: 'geral', name: '💬 Geral' },
9
+ { id: 'tech', name: '💻 Tecnologia' },
10
+ { id: 'random', name: '🎲 Random' },
11
+ { id: 'vip', name: '⭐ VIP' }
12
+ ]
13
+
14
+ export function RoomChatDemo() {
15
+ const [text, setText] = useState('')
16
+ const messagesEndRef = useRef<HTMLDivElement>(null)
17
+
18
+ const defaultUsername = useMemo(() => {
19
+ const adj = ['Happy', 'Cool', 'Fast', 'Smart', 'Brave'][Math.floor(Math.random() * 5)]
20
+ const noun = ['Panda', 'Tiger', 'Eagle', 'Wolf', 'Bear'][Math.floor(Math.random() * 5)]
21
+ return `${adj}${noun}${Math.floor(Math.random() * 100)}`
22
+ }, [])
23
+
24
+ const chat = Live.use(LiveRoomChat, {
25
+ initialState: { ...LiveRoomChat.defaultState, username: defaultUsername }
26
+ })
27
+
28
+ const activeRoom = chat.$state.activeRoom
29
+ const activeMessages = activeRoom ? (chat.$state.messages[activeRoom] || []) : []
30
+
31
+ useEffect(() => {
32
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
33
+ }, [activeMessages.length])
34
+
35
+ const handleJoinRoom = async (roomId: string, roomName: string) => {
36
+ if (chat.$rooms.includes(roomId)) {
37
+ await chat.switchRoom({ roomId })
38
+ } else {
39
+ await chat.joinRoom({ roomId, roomName })
40
+ }
41
+ }
42
+
43
+ const handleSendMessage = async () => {
44
+ if (!text.trim() || !activeRoom) return
45
+ await chat.sendMessage({ text })
46
+ setText('')
47
+ }
48
+
49
+ return (
50
+ <div className="flex h-[600px] bg-gray-900 rounded-2xl overflow-hidden border border-white/10">
51
+ {/* Sidebar */}
52
+ <div className="w-64 bg-gray-800/50 border-r border-white/10 flex flex-col">
53
+ <div className="p-4 border-b border-white/10">
54
+ <h2 className="text-lg font-bold text-white mb-2">💬 Room Chat</h2>
55
+ <div className="flex items-center gap-2">
56
+ <div className={`w-2 h-2 rounded-full ${chat.$connected ? 'bg-emerald-400' : 'bg-red-400'}`} />
57
+ <span className="text-sm text-gray-400">{chat.$state.username}</span>
58
+ </div>
59
+ </div>
60
+
61
+ <div className="flex-1 overflow-auto p-2">
62
+ <p className="text-xs text-gray-500 px-2 py-1">SALAS</p>
63
+ {AVAILABLE_ROOMS.map(room => {
64
+ const isJoined = chat.$rooms.includes(room.id)
65
+ const isActive = activeRoom === room.id
66
+
67
+ return (
68
+ <div
69
+ key={room.id}
70
+ onClick={() => handleJoinRoom(room.id, room.name)}
71
+ className={`
72
+ flex items-center justify-between px-3 py-2 rounded-lg cursor-pointer mb-1 transition-all group
73
+ ${isActive ? 'bg-purple-500/20 text-purple-300' : isJoined ? 'bg-white/5 text-gray-300 hover:bg-white/10' : 'text-gray-500 hover:bg-white/5'}
74
+ `}
75
+ >
76
+ <span className="flex items-center gap-2">
77
+ {room.name}
78
+ {isJoined && <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />}
79
+ </span>
80
+ {isJoined && !isActive && (
81
+ <button
82
+ onClick={(e) => { e.stopPropagation(); chat.leaveRoom({ roomId: room.id }) }}
83
+ className="opacity-0 group-hover:opacity-100 text-red-400 hover:text-red-300 text-xs"
84
+ >✕</button>
85
+ )}
86
+ </div>
87
+ )
88
+ })}
89
+ </div>
90
+
91
+ <div className="p-3 border-t border-white/10">
92
+ <p className="text-xs text-gray-500">Em {chat.$rooms.length} sala(s)</p>
93
+ </div>
94
+ </div>
95
+
96
+ {/* Chat Area */}
97
+ <div className="flex-1 flex flex-col">
98
+ {activeRoom ? (
99
+ <>
100
+ <div className="px-4 py-3 border-b border-white/10 flex items-center justify-between">
101
+ <div>
102
+ <h3 className="text-white font-semibold">
103
+ {chat.$state.rooms.find(r => r.id === activeRoom)?.name || activeRoom}
104
+ </h3>
105
+ <p className="text-xs text-gray-500">{activeMessages.length} mensagens</p>
106
+ </div>
107
+ <button
108
+ onClick={() => chat.leaveRoom({ roomId: activeRoom })}
109
+ className="px-3 py-1 text-sm bg-red-500/20 text-red-300 rounded-lg hover:bg-red-500/30"
110
+ >Sair</button>
111
+ </div>
112
+
113
+ <div className="flex-1 overflow-auto p-4 space-y-3">
114
+ {activeMessages.length === 0 ? (
115
+ <div className="text-center text-gray-500 py-8">
116
+ <p>Nenhuma mensagem ainda</p>
117
+ <p className="text-sm">Seja o primeiro a enviar!</p>
118
+ </div>
119
+ ) : (
120
+ activeMessages.map(msg => (
121
+ <div key={msg.id} className={`flex flex-col ${msg.user === chat.$state.username ? 'items-end' : 'items-start'}`}>
122
+ <div className={`max-w-[80%] rounded-2xl px-4 py-2 ${msg.user === chat.$state.username ? 'bg-purple-500/30 text-purple-100' : 'bg-white/10 text-gray-200'}`}>
123
+ <p className="text-xs text-gray-400 mb-1">{msg.user}</p>
124
+ <p>{msg.text}</p>
125
+ </div>
126
+ <span className="text-xs text-gray-600 mt-1">{new Date(msg.timestamp).toLocaleTimeString()}</span>
127
+ </div>
128
+ ))
129
+ )}
130
+ <div ref={messagesEndRef} />
131
+ </div>
132
+
133
+ <div className="p-4 border-t border-white/10">
134
+ <div className="flex gap-2">
135
+ <input
136
+ value={text}
137
+ onChange={(e) => setText(e.target.value)}
138
+ onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendMessage() } }}
139
+ placeholder="Digite uma mensagem..."
140
+ 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"
141
+ />
142
+ <button
143
+ onClick={handleSendMessage}
144
+ disabled={!text.trim()}
145
+ className="px-6 py-2 rounded-xl bg-purple-500/30 text-purple-200 hover:bg-purple-500/40 disabled:opacity-50"
146
+ >Enviar</button>
147
+ </div>
148
+ </div>
149
+ </>
150
+ ) : (
151
+ <div className="flex-1 flex items-center justify-center text-gray-500">
152
+ <div className="text-center">
153
+ <p className="text-4xl mb-4">👈</p>
154
+ <p>Selecione uma sala para começar</p>
155
+ </div>
156
+ </div>
157
+ )}
158
+ </div>
159
+ </div>
160
+ )
161
+ }
@@ -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
1
  import { StrictMode } from 'react'
2
2
  import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router'
3
4
  import './index.css'
4
5
  import App from './App.tsx'
5
6
 
6
7
  createRoot(document.getElementById('root')!).render(
7
8
  <StrictMode>
8
- <App />
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
9
12
  </StrictMode>,
10
13
  )
@@ -0,0 +1,108 @@
1
+ import type { ReactNode } from 'react'
2
+ import { BackButton } from '../components/BackButton'
3
+
4
+ export function ApiTestPage({
5
+ apiResponse,
6
+ isLoading,
7
+ onHealth,
8
+ onGetUsers,
9
+ onCreateUser
10
+ }: {
11
+ apiResponse: string
12
+ isLoading: boolean
13
+ onHealth: () => void
14
+ onGetUsers: () => void
15
+ onCreateUser: () => void
16
+ }) {
17
+ return (
18
+ <div className="container mx-auto px-4 py-8 max-w-4xl">
19
+ <div className="flex items-center gap-4 mb-8">
20
+ <BackButton />
21
+ <h1 className="text-3xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
22
+ Eden Treaty API Test
23
+ </h1>
24
+ </div>
25
+
26
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
27
+ <ActionCard
28
+ icon="🏥"
29
+ title="GET /api/health"
30
+ subtitle="Health Check"
31
+ onClick={onHealth}
32
+ disabled={isLoading}
33
+ className="bg-emerald-500/20 border-emerald-500/30 text-emerald-300 hover:bg-emerald-500/30"
34
+ />
35
+ <ActionCard
36
+ icon="👥"
37
+ title="GET /api/users"
38
+ subtitle="List Users"
39
+ onClick={onGetUsers}
40
+ disabled={isLoading}
41
+ className="bg-blue-500/20 border-blue-500/30 text-blue-300 hover:bg-blue-500/30"
42
+ />
43
+ <ActionCard
44
+ icon="➕"
45
+ title="POST /api/users"
46
+ subtitle="Create User"
47
+ onClick={onCreateUser}
48
+ disabled={isLoading}
49
+ className="bg-purple-500/20 border-purple-500/30 text-purple-300 hover:bg-purple-500/30"
50
+ />
51
+ </div>
52
+
53
+ <div className="bg-black/40 backdrop-blur-sm border border-white/10 rounded-2xl p-6">
54
+ <div className="flex items-center justify-between mb-4">
55
+ <h2 className="text-lg font-semibold text-white">Response</h2>
56
+ {isLoading && (
57
+ <div className="flex items-center gap-2 text-yellow-400">
58
+ <div className="w-4 h-4 border-2 border-yellow-400 border-t-transparent rounded-full animate-spin"></div>
59
+ Loading...
60
+ </div>
61
+ )}
62
+ </div>
63
+ <pre className="bg-black/60 rounded-xl p-4 overflow-auto max-h-96 text-sm font-mono">
64
+ <code className="text-green-400">
65
+ {apiResponse || '// Click a button above to test the API\\n// Type inference works automatically!'}
66
+ </code>
67
+ </pre>
68
+ </div>
69
+
70
+ <div className="mt-8 bg-white/5 border border-white/10 rounded-xl p-6">
71
+ <h3 className="text-lg font-semibold text-white mb-3">How it works</h3>
72
+ <div className="text-gray-400 text-sm space-y-2">
73
+ <p>OK <code className="text-purple-400">api.health.get()</code> - Full type inference from server</p>
74
+ <p>OK <code className="text-purple-400">api.users.post({'{ name, email }'})</code> - Request body is typed</p>
75
+ <p>OK <code className="text-purple-400">{'{ data, error }'}</code> - Response is typed automatically</p>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ )
80
+ }
81
+
82
+ function ActionCard({
83
+ icon,
84
+ title,
85
+ subtitle,
86
+ onClick,
87
+ disabled,
88
+ className
89
+ }: {
90
+ icon: ReactNode
91
+ title: string
92
+ subtitle: string
93
+ onClick: () => void
94
+ disabled: boolean
95
+ className: string
96
+ }) {
97
+ return (
98
+ <button
99
+ onClick={onClick}
100
+ disabled={disabled}
101
+ className={`px-6 py-4 border rounded-xl font-medium transition-all disabled:opacity-50 ${className}`}
102
+ >
103
+ <div className="text-2xl mb-2">{icon}</div>
104
+ <div>{title}</div>
105
+ <div className="text-xs opacity-70 mt-1">{subtitle}</div>
106
+ </button>
107
+ )
108
+ }