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
@@ -1,1201 +1,369 @@
1
- import type { CliCommand } from "@/core/plugins/types";
1
+ import type { CliCommand } from "@core/plugins/types";
2
2
  import { promises as fs } from "fs";
3
3
  import path from "path";
4
4
 
5
- // Component templates for different types
6
- const getServerTemplate = (componentName: string, type: string, room?: string) => {
7
- const roomComment = room ? `\n // Default room: ${room}` : '';
8
- const roomInit = room ? `\n this.room = '${room}';` : '';
5
+ // ===== SERVER TEMPLATES =====
6
+
7
+ const getServerTemplate = (name: string, type: string, room?: string, hasClient = true) => {
8
+ // Se room for especificado, precisa de constructor para definir this.room
9
+ const needsConstructor = !!room;
10
+ const constructorBlock = needsConstructor ? `
11
+ constructor(initialState: Partial<typeof ${name}.defaultState> = {}, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) {
12
+ super(initialState, ws, options)
13
+ this.room = '${room}'
14
+ }
15
+ ` : '';
16
+
17
+ const clientLink = hasClient ? `
18
+ // Componente Cliente (Ctrl+Click para navegar)
19
+ import type { ${name}Demo as _Client } from '@client/src/live/${name}'
20
+ ` : '';
9
21
 
10
22
  switch (type) {
11
23
  case 'counter':
12
- return `// 🔥 ${componentName} - Counter Live Component
13
- import { LiveComponent } from "@/core/types/types";
24
+ return `// ${name} - Contador
25
+ import { LiveComponent${needsConstructor ? ', type FluxStackWebSocket' : ''} } from '@core/types/types'
26
+ ${clientLink}
14
27
 
15
- interface ${componentName}State {
16
- count: number;
17
- title: string;
18
- step: number;
19
- lastUpdated: Date;
20
- }
21
-
22
- export class ${componentName}Component extends LiveComponent<${componentName}State> {
23
- constructor(initialState: ${componentName}State, ws: any, options?: { room?: string; userId?: string }) {
24
- super({
25
- count: 0,
26
- title: "${componentName} Counter",
27
- step: 1,
28
- lastUpdated: new Date(),
29
- ...initialState
30
- }, ws, options);${roomComment}${roomInit}
31
-
32
- console.log(\`🔢 \${this.constructor.name} component created: \${this.id}\`);
28
+ export class ${name} extends LiveComponent<typeof ${name}.defaultState> {
29
+ static componentName = '${name}'
30
+ static defaultState = {
31
+ count: 0
33
32
  }
34
-
35
- async increment(amount: number = this.state.step) {
36
- const newCount = this.state.count + amount;
37
-
38
- this.setState({
39
- count: newCount,
40
- lastUpdated: new Date()
41
- });
42
-
43
- // Broadcast to room for multi-user sync
44
- if (this.room) {
45
- this.broadcast('COUNTER_INCREMENTED', {
46
- count: newCount,
47
- amount,
48
- userId: this.userId
49
- });
50
- }
51
-
52
- console.log(\`🔢 Counter incremented to \${newCount} (step: \${amount})\`);
53
- return { success: true, count: newCount };
33
+ ${constructorBlock}
34
+ async increment() {
35
+ this.state.count++
36
+ return { success: true, count: this.state.count }
54
37
  }
55
38
 
56
- async decrement(amount: number = this.state.step) {
57
- const newCount = Math.max(0, this.state.count - amount);
58
-
59
- this.setState({
60
- count: newCount,
61
- lastUpdated: new Date()
62
- });
63
-
64
- if (this.room) {
65
- this.broadcast('COUNTER_DECREMENTED', {
66
- count: newCount,
67
- amount,
68
- userId: this.userId
69
- });
70
- }
71
-
72
- console.log(\`🔢 Counter decremented to \${newCount} (step: \${amount})\`);
73
- return { success: true, count: newCount };
39
+ async decrement() {
40
+ this.state.count--
41
+ return { success: true, count: this.state.count }
74
42
  }
75
43
 
76
44
  async reset() {
77
- this.setState({
78
- count: 0,
79
- lastUpdated: new Date()
80
- });
81
-
82
- if (this.room) {
83
- this.broadcast('COUNTER_RESET', { userId: this.userId });
84
- }
85
-
86
- console.log(\`🔢 Counter reset\`);
87
- return { success: true, count: 0 };
88
- }
89
-
90
- async setStep(step: number) {
91
- this.setState({
92
- step: Math.max(1, step),
93
- lastUpdated: new Date()
94
- });
95
-
96
- return { success: true, step };
45
+ this.state.count = 0
46
+ return { success: true }
97
47
  }
98
-
99
- async updateTitle(data: { title: string }) {
100
- const newTitle = data.title.trim();
101
-
102
- if (!newTitle || newTitle.length > 50) {
103
- throw new Error('Title must be 1-50 characters');
104
- }
105
-
106
- this.setState({
107
- title: newTitle,
108
- lastUpdated: new Date()
109
- });
110
-
111
- return { success: true, title: newTitle };
112
- }
113
- }`;
114
-
115
- case 'form':
116
- return `// 🔥 ${componentName} - Form Live Component
117
- import { LiveComponent } from "@/core/types/types";
118
-
119
- interface ${componentName}State {
120
- formData: Record<string, any>;
121
- errors: Record<string, string>;
122
- isSubmitting: boolean;
123
- isValid: boolean;
124
- lastUpdated: Date;
125
48
  }
49
+ `;
126
50
 
127
- export class ${componentName}Component extends LiveComponent<${componentName}State> {
128
- constructor(initialState: ${componentName}State, ws: any, options?: { room?: string; userId?: string }) {
129
- super({
130
- formData: {},
131
- errors: {},
132
- isSubmitting: false,
133
- isValid: false,
134
- lastUpdated: new Date(),
135
- ...initialState
136
- }, ws, options);${roomComment}${roomInit}
137
-
138
- console.log(\`📝 \${this.constructor.name} component created: \${this.id}\`);
139
- }
140
-
141
- async updateField(data: { field: string; value: any }) {
142
- const { field, value } = data;
143
- const newFormData = { ...this.state.formData, [field]: value };
144
- const newErrors = { ...this.state.errors };
145
-
146
- // Clear error for this field
147
- delete newErrors[field];
148
-
149
- // Basic validation example
150
- if (field === 'email' && value && !this.isValidEmail(value)) {
151
- newErrors[field] = 'Invalid email format';
152
- }
153
-
154
- this.setState({
155
- formData: newFormData,
156
- errors: newErrors,
157
- isValid: Object.keys(newErrors).length === 0,
158
- lastUpdated: new Date()
159
- });
160
-
161
- return { success: true, field, value };
162
- }
163
-
164
- async submitForm() {
165
- this.setState({ isSubmitting: true });
166
-
167
- try {
168
- // Simulate form submission
169
- await this.sleep(1000);
170
-
171
- // Validate all fields
172
- const errors = this.validateForm(this.state.formData);
173
-
174
- if (Object.keys(errors).length > 0) {
175
- this.setState({
176
- errors,
177
- isSubmitting: false,
178
- isValid: false
179
- });
180
- return { success: false, errors };
181
- }
182
-
183
- // Success
184
- this.setState({
185
- isSubmitting: false,
186
- errors: {},
187
- isValid: true,
188
- lastUpdated: new Date()
189
- });
190
-
191
- if (this.room) {
192
- this.broadcast('FORM_SUBMITTED', {
193
- formData: this.state.formData,
194
- userId: this.userId
195
- });
196
- }
197
-
198
- console.log(\`📝 Form submitted successfully\`);
199
- return { success: true, data: this.state.formData };
200
-
201
- } catch (error: any) {
202
- this.setState({ isSubmitting: false });
203
- throw error;
204
- }
205
- }
206
-
207
- async resetForm() {
208
- this.setState({
209
- formData: {},
210
- errors: {},
211
- isSubmitting: false,
212
- isValid: false,
213
- lastUpdated: new Date()
214
- });
215
-
216
- return { success: true };
217
- }
218
-
219
- private validateForm(data: Record<string, any>): Record<string, string> {
220
- const errors: Record<string, string> = {};
221
-
222
- // Add your validation rules here
223
- if (!data.name || data.name.trim().length < 2) {
224
- errors.name = 'Name must be at least 2 characters';
225
- }
226
-
227
- if (!data.email || !this.isValidEmail(data.email)) {
228
- errors.email = 'Valid email is required';
51
+ case 'form':
52
+ return `// ${name} - Formulário
53
+ import { LiveComponent${needsConstructor ? ', type FluxStackWebSocket' : ''} } from '@core/types/types'
54
+ ${clientLink}
55
+
56
+ export class ${name} extends LiveComponent<typeof ${name}.defaultState> {
57
+ static componentName = '${name}'
58
+ static defaultState = {
59
+ name: '',
60
+ email: '',
61
+ message: '',
62
+ submitted: false,
63
+ submittedAt: null as string | null
64
+ }
65
+ ${constructorBlock}
66
+ async submit() {
67
+ if (!this.state.name?.trim() || !this.state.email?.trim()) {
68
+ throw new Error('Nome e email são obrigatórios')
229
69
  }
230
-
231
- return errors;
232
- }
233
-
234
- private isValidEmail(email: string): boolean {
235
- return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);
70
+ this.setState({ submitted: true, submittedAt: new Date().toISOString() })
71
+ console.log(\`📝 Form submitted:\`, { name: this.state.name, email: this.state.email })
72
+ return { success: true, data: this.state }
236
73
  }
237
74
 
238
- private sleep(ms: number): Promise<void> {
239
- return new Promise(resolve => setTimeout(resolve, ms));
75
+ async reset() {
76
+ this.setState({ ...${name}.defaultState })
77
+ return { success: true }
240
78
  }
241
- }`;
242
-
243
- case 'chat':
244
- return `// 🔥 ${componentName} - Chat Live Component
245
- import { LiveComponent } from "@/core/types/types";
246
-
247
- interface Message {
248
- id: string;
249
- text: string;
250
- userId: string;
251
- username: string;
252
- timestamp: Date;
253
79
  }
80
+ `;
254
81
 
255
- interface ${componentName}State {
256
- messages: Message[];
257
- users: Record<string, { username: string; isOnline: boolean }>;
258
- currentMessage: string;
259
- isTyping: Record<string, boolean>;
260
- lastUpdated: Date;
261
- }
262
-
263
- export class ${componentName}Component extends LiveComponent<${componentName}State> {
264
- constructor(initialState: ${componentName}State, ws: any, options?: { room?: string; userId?: string }) {
265
- super({
266
- messages: [],
267
- users: {},
268
- currentMessage: "",
269
- isTyping: {},
270
- lastUpdated: new Date(),
271
- ...initialState
272
- }, ws, options);${roomComment}${roomInit}
273
-
274
- console.log(\`💬 \${this.constructor.name} component created: \${this.id}\`);
275
-
276
- // Add user to online users
277
- if (this.userId) {
278
- this.addUser(this.userId, \`User \${this.userId.slice(-4)}\`);
279
- }
280
- }
281
-
282
- async sendMessage(data: { text: string; username?: string }) {
283
- const { text, username = \`User \${this.userId?.slice(-4) || 'Anonymous'}\` } = data;
284
-
285
- if (!text.trim()) {
286
- throw new Error('Message cannot be empty');
287
- }
288
-
289
- const message: Message = {
290
- id: \`msg-\${Date.now()}-\${Math.random().toString(36).substr(2, 9)}\`,
291
- text: text.trim(),
292
- userId: this.userId || 'anonymous',
293
- username,
294
- timestamp: new Date()
295
- };
296
-
297
- const newMessages = [...this.state.messages, message];
298
-
299
- this.setState({
300
- messages: newMessages.slice(-50), // Keep last 50 messages
301
- currentMessage: "",
302
- lastUpdated: new Date()
303
- });
304
-
305
- // Broadcast to all users in the room
306
- if (this.room) {
307
- this.broadcast('NEW_MESSAGE', {
308
- message,
309
- totalMessages: newMessages.length
310
- });
311
- }
312
-
313
- console.log(\`💬 Message sent: "\${text}" by \${username}\`);
314
- return { success: true, message };
315
- }
316
-
317
- async updateTyping(data: { isTyping: boolean; username?: string }) {
318
- const { isTyping, username = \`User \${this.userId?.slice(-4)}\` } = data;
319
- const userId = this.userId || 'anonymous';
320
-
321
- const newTyping = { ...this.state.isTyping };
322
-
323
- if (isTyping) {
324
- newTyping[userId] = true;
325
- } else {
326
- delete newTyping[userId];
327
- }
328
-
329
- this.setState({
330
- isTyping: newTyping,
331
- lastUpdated: new Date()
332
- });
333
-
334
- // Broadcast typing status
335
- if (this.room) {
336
- this.broadcast('USER_TYPING', {
337
- userId,
338
- username,
339
- isTyping
340
- });
341
- }
342
-
343
- // Auto-clear typing after 3 seconds
344
- if (isTyping) {
345
- setTimeout(() => {
346
- this.updateTyping({ isTyping: false, username });
347
- }, 3000);
348
- }
349
-
350
- return { success: true };
351
- }
352
-
353
- async joinRoom(data: { username: string }) {
354
- const { username } = data;
355
-
356
- this.addUser(this.userId || 'anonymous', username);
357
-
358
- if (this.room) {
359
- this.broadcast('USER_JOINED', {
360
- userId: this.userId,
361
- username,
362
- timestamp: new Date()
363
- });
82
+ case 'chat':
83
+ return `// ${name} - Chat
84
+ import { LiveComponent${needsConstructor ? ', type FluxStackWebSocket' : ''} } from '@core/types/types'
85
+ ${clientLink}
86
+
87
+ export class ${name} extends LiveComponent<typeof ${name}.defaultState> {
88
+ static componentName = '${name}'
89
+ static defaultState = {
90
+ messages: [] as Array<{ id: string; text: string; username: string; timestamp: string }>,
91
+ username: '',
92
+ currentMessage: ''
93
+ }
94
+ ${constructorBlock}
95
+ async sendMessage(payload: { text: string }) {
96
+ if (!payload.text?.trim()) throw new Error('Message cannot be empty')
97
+ const message = {
98
+ id: \`msg-\${Date.now()}-\${Math.random().toString(36).slice(2, 9)}\`,
99
+ text: payload.text.trim(),
100
+ username: this.state.username || 'Anonymous',
101
+ timestamp: new Date().toISOString()
364
102
  }
365
-
366
- return { success: true, username };
103
+ this.setState({ messages: [...this.state.messages.slice(-49), message], currentMessage: '' })
104
+ if (this.room) this.broadcast('NEW_MESSAGE', { message })
105
+ return { success: true, message }
367
106
  }
368
107
 
369
- async leaveRoom() {
370
- const userId = this.userId || 'anonymous';
371
- const users = { ...this.state.users };
372
- delete users[userId];
373
-
374
- this.setState({
375
- users,
376
- lastUpdated: new Date()
377
- });
378
-
379
- if (this.room) {
380
- this.broadcast('USER_LEFT', {
381
- userId,
382
- timestamp: new Date()
383
- });
108
+ async setUsername(payload: { username: string }) {
109
+ if (!payload.username?.trim() || payload.username.length > 20) {
110
+ throw new Error('Username must be 1-20 characters')
384
111
  }
385
-
386
- return { success: true };
387
- }
388
-
389
- private addUser(userId: string, username: string) {
390
- const users = {
391
- ...this.state.users,
392
- [userId]: { username, isOnline: true }
393
- };
394
-
395
- this.setState({
396
- users,
397
- lastUpdated: new Date()
398
- });
399
- }
400
-
401
- public destroy() {
402
- this.leaveRoom();
403
- super.destroy();
112
+ this.setState({ username: payload.username.trim() })
113
+ return { success: true }
404
114
  }
405
- }`;
115
+ }
116
+ `;
406
117
 
407
118
  default: // basic
408
- return `// 🔥 ${componentName} - Live Component
409
- import { LiveComponent } from "@/core/types/types";
119
+ return `// ${name} - Live Component
120
+ import { LiveComponent${needsConstructor ? ', type FluxStackWebSocket' : ''} } from '@core/types/types'
121
+ ${clientLink}
410
122
 
411
- interface ${componentName}State {
412
- message: string;
413
- count: number;
414
- lastUpdated: Date;
415
- }
416
-
417
- export class ${componentName}Component extends LiveComponent<${componentName}State> {
418
- constructor(initialState: ${componentName}State, ws: any, options?: { room?: string; userId?: string }) {
419
- super({
420
- message: "Hello from ${componentName}!",
421
- count: 0,
422
- lastUpdated: new Date(),
423
- ...initialState
424
- }, ws, options);${roomComment}${roomInit}
425
-
426
- console.log(\`🔥 \${this.constructor.name} component created: \${this.id}\`);
123
+ export class ${name} extends LiveComponent<typeof ${name}.defaultState> {
124
+ static componentName = '${name}'
125
+ static defaultState = {
126
+ message: 'Hello from ${name}!',
127
+ count: 0
427
128
  }
428
-
129
+ ${constructorBlock}
429
130
  async updateMessage(payload: { message: string }) {
430
- const { message } = payload;
431
-
432
- if (!message || message.trim().length === 0) {
433
- throw new Error('Message cannot be empty');
434
- }
435
-
436
- this.setState({
437
- message: message.trim(),
438
- lastUpdated: new Date()
439
- });
440
-
441
- // Broadcast to room if in multi-user mode
442
- if (this.room) {
443
- this.broadcast('MESSAGE_UPDATED', {
444
- message: message.trim(),
445
- userId: this.userId
446
- });
447
- }
448
-
449
- console.log(\`📝 Message updated: "\${message}"\`);
450
- return { success: true, message: message.trim() };
451
- }
452
-
453
- async incrementCounter() {
454
- const newCount = this.state.count + 1;
455
-
456
- this.setState({
457
- count: newCount,
458
- lastUpdated: new Date()
459
- });
460
-
461
- if (this.room) {
462
- this.broadcast('COUNTER_INCREMENTED', {
463
- count: newCount,
464
- userId: this.userId
465
- });
466
- }
467
-
468
- return { success: true, count: newCount };
131
+ if (!payload.message?.trim()) throw new Error('Message cannot be empty')
132
+ this.state.message = payload.message.trim()
133
+ return { success: true }
469
134
  }
470
135
 
471
- async resetData() {
472
- this.setState({
473
- message: "Hello from ${componentName}!",
474
- count: 0,
475
- lastUpdated: new Date()
476
- });
477
-
478
- return { success: true };
136
+ async increment() {
137
+ this.state.count++
138
+ return { success: true, count: this.state.count }
479
139
  }
480
140
 
481
- async getData() {
482
- return {
483
- success: true,
484
- data: {
485
- ...this.state,
486
- componentId: this.id,
487
- room: this.room,
488
- userId: this.userId
489
- }
490
- };
141
+ async reset() {
142
+ this.setState({ ...${name}.defaultState })
143
+ return { success: true }
491
144
  }
492
- }`;
145
+ }
146
+ `;
493
147
  }
494
148
  };
495
149
 
496
- const getClientTemplate = (componentName: string, type: string, room?: string) => {
497
- const roomProps = room ? `, { room: '${room}' }` : '';
150
+ // ===== CLIENT TEMPLATES =====
498
151
 
152
+ const getClientTemplate = (name: string, type: string) => {
499
153
  switch (type) {
500
154
  case 'counter':
501
- return `// 🔥 ${componentName} - Counter Client Component
502
- import React from 'react';
503
- import { useHybridLiveComponent } from 'fluxstack';
504
-
505
- interface ${componentName}State {
506
- count: number;
507
- title: string;
508
- step: number;
509
- lastUpdated: Date;
510
- }
155
+ return `// 🔥 ${name}
156
+ import { Live } from '@/core/client'
157
+ import { ${name} } from '@server/live/${name}'
511
158
 
512
- const initialState: ${componentName}State = {
513
- count: 0,
514
- title: "${componentName} Counter",
515
- step: 1,
516
- lastUpdated: new Date(),
517
- };
518
-
519
- export function ${componentName}() {
520
- const { state, call, connected, loading } = useHybridLiveComponent<${componentName}State>('${componentName}', initialState${roomProps});
159
+ export function ${name}Demo() {
160
+ const counter = Live.use(${name}, {
161
+ initialState: ${name}.defaultState
162
+ })
521
163
 
522
- if (!connected) {
523
- return (
524
- <div className="flex items-center justify-center p-8 border-2 border-dashed border-gray-300 rounded-lg">
525
- <div className="text-center">
526
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
527
- <p className="text-gray-600">Connecting to ${componentName}...</p>
528
- </div>
529
- </div>
530
- );
531
- }
164
+ if (!counter.$connected) return <div className="p-8 text-center text-gray-500">Conectando...</div>
532
165
 
533
166
  return (
534
- <div className="bg-white border border-gray-200 rounded-lg shadow-sm p-6 m-4 relative">
535
- <div className="flex items-center justify-between mb-4">
536
- <h2 className="text-2xl font-bold text-gray-800">{state.title}</h2>
537
- <span className={
538
- \`px-2 py-1 rounded-full text-xs font-medium \${
539
- connected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
540
- }\`
541
- }>
542
- {connected ? '🟢 Connected' : '🔴 Disconnected'}
543
- </span>
544
- </div>
545
-
546
- <div className="text-center mb-6">
547
- <div className="text-6xl font-bold text-blue-600 mb-2">{state.count}</div>
548
- <p className="text-gray-600">Current Count</p>
549
- </div>
550
-
551
- <div className="flex gap-2 justify-center mb-4">
552
- <button
553
- onClick={() => call('decrement')}
554
- disabled={loading || state.count <= 0}
555
- className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed"
556
- >
557
- ➖ Decrement
558
- </button>
559
-
560
- <button
561
- onClick={() => call('increment')}
562
- disabled={loading}
563
- className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
564
- >
565
- ➕ Increment
566
- </button>
567
-
568
- <button
569
- onClick={() => call('reset')}
570
- disabled={loading}
571
- className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
572
- >
573
- 🔄 Reset
574
- </button>
167
+ <div className="p-6 bg-white rounded-xl shadow-sm border">
168
+ <h2 className="text-xl font-bold mb-4">${name}</h2>
169
+ <div className="text-5xl font-bold text-blue-600 text-center mb-6">{counter.count}</div>
170
+ <div className="flex gap-2 justify-center">
171
+ <button onClick={() => counter.decrement()} disabled={counter.count <= 0} className="px-4 py-2 bg-red-500 text-white rounded-lg disabled:opacity-50">-</button>
172
+ <button onClick={() => counter.increment()} className="px-4 py-2 bg-blue-500 text-white rounded-lg">+</button>
173
+ <button onClick={() => counter.reset()} className="px-4 py-2 bg-gray-500 text-white rounded-lg">Reset</button>
575
174
  </div>
576
-
577
- <div className="border-t pt-4">
578
- <div className="flex gap-2 items-center mb-2">
579
- <label className="text-sm font-medium text-gray-700">Step Size:</label>
580
- <input
581
- type="number"
582
- min="1"
583
- max="10"
584
- value={state.step}
585
- onChange={(e) => call('setStep', parseInt(e.target.value) || 1)}
586
- className="w-20 px-2 py-1 border rounded"
587
- disabled={loading}
588
- />
589
- </div>
590
-
591
- <div className="flex gap-2 items-center">
592
- <label className="text-sm font-medium text-gray-700">Title:</label>
593
- <input
594
- type="text"
595
- value={state.title}
596
- onChange={(e) => call('updateTitle', { title: e.target.value })}
597
- className="flex-1 px-2 py-1 border rounded"
598
- disabled={loading}
599
- maxLength={50}
600
- />
601
- </div>
602
- </div>
603
-
604
- <div className="mt-4 text-xs text-gray-500 text-center">
605
- Last updated: {new Date(state.lastUpdated).toLocaleTimeString()}
606
- </div>
607
-
608
- {loading && (
609
- <div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center rounded-lg">
610
- <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
611
- </div>
612
- )}
613
175
  </div>
614
- );
615
- }`;
176
+ )
177
+ }
178
+ `;
616
179
 
617
180
  case 'form':
618
- return `// 🔥 ${componentName} - Form Client Component
619
- import React from 'react';
620
- import { useHybridLiveComponent } from 'fluxstack';
621
-
622
- interface ${componentName}State {
623
- formData: Record<string, any>;
624
- errors: Record<string, string>;
625
- isSubmitting: boolean;
626
- isValid: boolean;
627
- lastUpdated: Date;
628
- }
181
+ return `// 🔥 ${name}
182
+ import { Live } from '@/core/client'
183
+ import { ${name} } from '@server/live/${name}'
629
184
 
630
- const initialState: ${componentName}State = {
631
- formData: {},
632
- errors: {},
633
- isSubmitting: false,
634
- isValid: false,
635
- lastUpdated: new Date(),
636
- };
185
+ export function ${name}Demo() {
186
+ const form = Live.use(${name}, {
187
+ initialState: ${name}.defaultState
188
+ })
637
189
 
638
- export function ${componentName}() {
639
- const { state, call, connected, loading } = useHybridLiveComponent<${componentName}State>('${componentName}', initialState${roomProps});
190
+ if (!form.$connected) return <div className="p-8 text-center text-gray-500">Conectando...</div>
640
191
 
641
- if (!connected) {
192
+ if (form.submitted) {
642
193
  return (
643
- <div className="flex items-center justify-center p-8 border-2 border-dashed border-gray-300 rounded-lg">
644
- <div className="text-center">
645
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
646
- <p className="text-gray-600">Connecting to ${componentName}...</p>
647
- </div>
194
+ <div className="p-6 bg-green-50 rounded-xl text-center">
195
+ <h2 className="text-xl font-bold text-green-700 mb-2">Enviado!</h2>
196
+ <p className="text-gray-600">Obrigado, {form.name}!</p>
197
+ <button onClick={() => form.reset()} className="mt-4 px-4 py-2 bg-green-500 text-white rounded-lg">Novo</button>
648
198
  </div>
649
- );
199
+ )
650
200
  }
651
201
 
652
- const handleFieldChange = (field: string, value: any) => {
653
- call('updateField', { field, value });
654
- };
655
-
656
- const handleSubmit = async (e: React.FormEvent) => {
657
- e.preventDefault();
658
- try {
659
- const result = await call('submitForm');
660
- if (result?.success) {
661
- alert('Form submitted successfully!');
662
- }
663
- } catch (error) {
664
- console.error('Submit error:', error);
665
- }
666
- };
667
-
668
- const handleReset = () => {
669
- call('resetForm');
670
- };
671
-
672
202
  return (
673
- <div className="bg-white border border-gray-200 rounded-lg shadow-sm p-6 m-4 max-w-md mx-auto">
674
- <div className="flex items-center justify-between mb-4">
675
- <h2 className="text-2xl font-bold text-gray-800">${componentName} Form</h2>
676
- <span className={
677
- \`px-2 py-1 rounded-full text-xs font-medium \${
678
- connected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
679
- }\`
680
- }>
681
- {connected ? '🟢 Connected' : '🔴 Disconnected'}
682
- </span>
683
- </div>
684
-
685
- <form onSubmit={handleSubmit} className="space-y-4">
686
- <div>
687
- <label className="block text-sm font-medium text-gray-700 mb-1">
688
- Name *
689
- </label>
690
- <input
691
- type="text"
692
- value={state.formData.name || ''}
693
- onChange={(e) => handleFieldChange('name', e.target.value)}
694
- className={\`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 \${
695
- state.errors.name ? 'border-red-500' : 'border-gray-300'
696
- }\`}
697
- disabled={loading || state.isSubmitting}
698
- placeholder="Enter your name"
699
- />
700
- {state.errors.name && (
701
- <p className="text-red-500 text-xs mt-1">{state.errors.name}</p>
702
- )}
703
- </div>
704
-
705
- <div>
706
- <label className="block text-sm font-medium text-gray-700 mb-1">
707
- Email *
708
- </label>
709
- <input
710
- type="email"
711
- value={state.formData.email || ''}
712
- onChange={(e) => handleFieldChange('email', e.target.value)}
713
- className={\`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 \${
714
- state.errors.email ? 'border-red-500' : 'border-gray-300'
715
- }\`}
716
- disabled={loading || state.isSubmitting}
717
- placeholder="Enter your email"
718
- />
719
- {state.errors.email && (
720
- <p className="text-red-500 text-xs mt-1">{state.errors.email}</p>
721
- )}
722
- </div>
723
-
724
- <div>
725
- <label className="block text-sm font-medium text-gray-700 mb-1">
726
- Message
727
- </label>
728
- <textarea
729
- value={state.formData.message || ''}
730
- onChange={(e) => handleFieldChange('message', e.target.value)}
731
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
732
- disabled={loading || state.isSubmitting}
733
- placeholder="Enter your message"
734
- rows={3}
735
- />
736
- </div>
737
-
203
+ <div className="p-6 bg-white rounded-xl shadow-sm border">
204
+ <h2 className="text-xl font-bold mb-4">${name}</h2>
205
+ <div className="space-y-4">
206
+ <input {...form.$field('name', { syncOn: 'blur' })} placeholder="Nome" className="w-full px-3 py-2 border rounded-lg" />
207
+ <input {...form.$field('email', { syncOn: 'change', debounce: 500 })} type="email" placeholder="Email" className="w-full px-3 py-2 border rounded-lg" />
208
+ <textarea {...form.$field('message', { syncOn: 'blur' })} placeholder="Mensagem" rows={3} className="w-full px-3 py-2 border rounded-lg" />
738
209
  <div className="flex gap-2">
739
- <button
740
- type="submit"
741
- disabled={loading || state.isSubmitting || !state.isValid}
742
- className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
743
- >
744
- {state.isSubmitting ? (
745
- <>
746
- <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
747
- Submitting...
748
- </>
749
- ) : (
750
- '📤 Submit'
751
- )}
752
- </button>
753
-
754
- <button
755
- type="button"
756
- onClick={handleReset}
757
- disabled={loading || state.isSubmitting}
758
- className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
759
- >
760
- 🔄 Reset
761
- </button>
210
+ <button onClick={async () => { try { await form.$sync(); await form.submit() } catch (e: any) { alert(e.message) }}} className="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg">Enviar</button>
211
+ <button onClick={() => form.reset()} className="px-4 py-2 bg-gray-500 text-white rounded-lg">Limpar</button>
762
212
  </div>
763
- </form>
764
-
765
- <div className="mt-4 text-xs text-gray-500 text-center">
766
- Last updated: {new Date(state.lastUpdated).toLocaleTimeString()}
767
213
  </div>
768
214
  </div>
769
- );
770
- }`;
771
-
772
- case 'chat':
773
- return `// 🔥 ${componentName} - Chat Client Component
774
- import React, { useState, useEffect, useRef } from 'react';
775
- import { useHybridLiveComponent } from 'fluxstack';
776
-
777
- interface Message {
778
- id: string;
779
- text: string;
780
- userId: string;
781
- username: string;
782
- timestamp: Date;
215
+ )
783
216
  }
217
+ `;
784
218
 
785
- interface ${componentName}State {
786
- messages: Message[];
787
- users: Record<string, { username: string; isOnline: boolean }>;
788
- currentMessage: string;
789
- isTyping: Record<string, boolean>;
790
- lastUpdated: Date;
791
- }
792
-
793
- const initialState: ${componentName}State = {
794
- messages: [],
795
- users: {},
796
- currentMessage: "",
797
- isTyping: {},
798
- lastUpdated: new Date(),
799
- };
219
+ case 'chat':
220
+ return `// 🔥 ${name}
221
+ import { useRef, useEffect } from 'react'
222
+ import { Live } from '@/core/client'
223
+ import { ${name} } from '@server/live/${name}'
800
224
 
801
- export function ${componentName}() {
802
- const { state, call, connected, loading } = useHybridLiveComponent<${componentName}State>('${componentName}', initialState${roomProps});
803
- const [username, setUsername] = useState(\`User\${Math.random().toString(36).substr(2, 4)}\`);
804
- const [hasJoined, setHasJoined] = useState(false);
805
- const messagesEndRef = useRef<HTMLDivElement>(null);
225
+ export function ${name}Demo() {
226
+ const chat = Live.use(${name}, {
227
+ initialState: ${name}.defaultState
228
+ })
229
+ const messagesEndRef = useRef<HTMLDivElement>(null)
806
230
 
807
- useEffect(() => {
808
- if (messagesEndRef.current) {
809
- messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
810
- }
811
- }, [state.messages]);
231
+ useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [chat.messages])
812
232
 
813
- if (!connected) {
814
- return (
815
- <div className="flex items-center justify-center p-8 border-2 border-dashed border-gray-300 rounded-lg">
816
- <div className="text-center">
817
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
818
- <p className="text-gray-600">Connecting to ${componentName}...</p>
819
- </div>
820
- </div>
821
- );
822
- }
233
+ if (!chat.$connected) return <div className="p-8 text-center text-gray-500">Conectando...</div>
823
234
 
824
- if (!hasJoined) {
235
+ if (!chat.username) {
825
236
  return (
826
- <div className="bg-white border border-gray-200 rounded-lg shadow-sm p-6 m-4 max-w-md mx-auto">
827
- <h2 className="text-2xl font-bold text-gray-800 mb-4">Join ${componentName}</h2>
828
- <div className="space-y-4">
829
- <div>
830
- <label className="block text-sm font-medium text-gray-700 mb-1">
831
- Username
832
- </label>
833
- <input
834
- type="text"
835
- value={username}
836
- onChange={(e) => setUsername(e.target.value)}
837
- className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
838
- placeholder="Enter your username"
839
- maxLength={20}
840
- />
841
- </div>
842
- <button
843
- onClick={async () => {
844
- if (username.trim()) {
845
- await call('joinRoom', { username: username.trim() });
846
- setHasJoined(true);
847
- }
848
- }}
849
- disabled={!username.trim() || loading}
850
- className="w-full px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
851
- >
852
- 💬 Join Chat
853
- </button>
854
- </div>
237
+ <div className="p-6 bg-white rounded-xl shadow-sm border max-w-md mx-auto">
238
+ <h2 className="text-xl font-bold mb-4">Entrar no ${name}</h2>
239
+ <input {...chat.$field('username', { syncOn: 'blur' })} placeholder="Seu nome" className="w-full px-3 py-2 border rounded-lg mb-4" maxLength={20} />
240
+ <button onClick={async () => { await chat.$sync(); if (chat.username.trim()) await chat.setUsername({ username: chat.username }) }} className="w-full px-4 py-2 bg-blue-500 text-white rounded-lg">Entrar</button>
855
241
  </div>
856
- );
242
+ )
857
243
  }
858
244
 
859
- const handleSendMessage = async (e: React.FormEvent) => {
860
- e.preventDefault();
861
- if (state.currentMessage.trim()) {
862
- await call('sendMessage', { text: state.currentMessage, username });
863
- }
864
- };
865
-
866
- const typingUsers = Object.keys(state.isTyping).filter(userId => state.isTyping[userId]);
867
-
868
245
  return (
869
- <div className="bg-white border border-gray-200 rounded-lg shadow-sm m-4 max-w-2xl mx-auto flex flex-col h-96">
870
- {/* Header */}
871
- <div className="flex items-center justify-between p-4 border-b border-gray-200">
872
- <h2 className="text-xl font-bold text-gray-800">${componentName}</h2>
873
- <div className="flex items-center gap-2">
874
- <span className="text-sm text-gray-600">
875
- {Object.keys(state.users).length} online
876
- </span>
877
- <span className={
878
- \`px-2 py-1 rounded-full text-xs font-medium \${
879
- connected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
880
- }\`
881
- }>
882
- {connected ? '🟢 Connected' : '🔴 Disconnected'}
883
- </span>
884
- </div>
885
- </div>
886
-
887
- {/* Messages */}
246
+ <div className="flex flex-col h-96 bg-white rounded-xl shadow-sm border">
247
+ <div className="p-4 border-b flex justify-between"><h2 className="font-bold">${name}</h2><span className="text-sm text-gray-500">{chat.username}</span></div>
888
248
  <div className="flex-1 overflow-y-auto p-4 space-y-2">
889
- {state.messages.map((message) => (
890
- <div
891
- key={message.id}
892
- className={\`flex \${message.username === username ? 'justify-end' : 'justify-start'}\`}
893
- >
894
- <div
895
- className={\`max-w-xs px-3 py-2 rounded-lg \${
896
- message.username === username
897
- ? 'bg-blue-500 text-white'
898
- : 'bg-gray-100 text-gray-800'
899
- }\`}
900
- >
901
- {message.username !== username && (
902
- <div className="text-xs font-medium mb-1">{message.username}</div>
903
- )}
904
- <div className="text-sm">{message.text}</div>
905
- <div className={\`text-xs mt-1 \${
906
- message.username === username ? 'text-blue-100' : 'text-gray-500'
907
- }\`}>
908
- {new Date(message.timestamp).toLocaleTimeString()}
909
- </div>
249
+ {chat.messages.map((msg) => (
250
+ <div key={msg.id} className={\`flex \${msg.username === chat.username ? 'justify-end' : 'justify-start'}\`}>
251
+ <div className={\`max-w-xs px-3 py-2 rounded-lg \${msg.username === chat.username ? 'bg-blue-500 text-white' : 'bg-gray-100'}\`}>
252
+ {msg.username !== chat.username && <div className="text-xs font-medium mb-1">{msg.username}</div>}
253
+ <div>{msg.text}</div>
910
254
  </div>
911
255
  </div>
912
256
  ))}
913
-
914
- {typingUsers.length > 0 && (
915
- <div className="text-xs text-gray-500 italic">
916
- {typingUsers.map(userId => state.users[userId]?.username || userId).join(', ')}
917
- {typingUsers.length === 1 ? ' is' : ' are'} typing...
918
- </div>
919
- )}
920
-
921
257
  <div ref={messagesEndRef} />
922
258
  </div>
923
-
924
- {/* Input */}
925
- <form onSubmit={handleSendMessage} className="p-4 border-t border-gray-200">
926
- <div className="flex gap-2">
927
- <input
928
- type="text"
929
- value={state.currentMessage}
930
- onChange={(e) => {
931
- call('updateField', { field: 'currentMessage', value: e.target.value });
932
- call('updateTyping', { isTyping: e.target.value.length > 0, username });
933
- }}
934
- className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
935
- placeholder="Type a message..."
936
- disabled={loading}
937
- maxLength={500}
938
- />
939
- <button
940
- type="submit"
941
- disabled={!state.currentMessage.trim() || loading}
942
- className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
943
- >
944
- 📤
945
- </button>
946
- </div>
259
+ <form onSubmit={async (e) => { e.preventDefault(); if (chat.currentMessage.trim()) await chat.sendMessage({ text: chat.currentMessage }) }} className="p-4 border-t flex gap-2">
260
+ <input {...chat.$field('currentMessage', { syncOn: 'change', debounce: 100 })} placeholder="Mensagem..." className="flex-1 px-3 py-2 border rounded-lg" />
261
+ <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded-lg">Enviar</button>
947
262
  </form>
948
263
  </div>
949
- );
950
- }`;
951
-
952
- default: // basic
953
- return `// 🔥 ${componentName} - Client Component
954
- import React from 'react';
955
- import { useHybridLiveComponent } from 'fluxstack';
956
-
957
- interface ${componentName}State {
958
- message: string;
959
- count: number;
960
- lastUpdated: Date;
264
+ )
961
265
  }
266
+ `;
962
267
 
963
- const initialState: ${componentName}State = {
964
- message: "Loading...",
965
- count: 0,
966
- lastUpdated: new Date(),
967
- };
268
+ default: // basic
269
+ return `// 🔥 ${name}
270
+ import { Live } from '@/core/client'
271
+ import { ${name} } from '@server/live/${name}'
968
272
 
969
- export function ${componentName}() {
970
- const { state, call, connected, loading } = useHybridLiveComponent<${componentName}State>('${componentName}', initialState${roomProps});
273
+ export function ${name}Demo() {
274
+ const component = Live.use(${name}, {
275
+ initialState: ${name}.defaultState
276
+ })
971
277
 
972
- if (!connected) {
973
- return (
974
- <div className="flex items-center justify-center p-8 border-2 border-dashed border-gray-300 rounded-lg">
975
- <div className="text-center">
976
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-2"></div>
977
- <p className="text-gray-600">Connecting to ${componentName}...</p>
978
- </div>
979
- </div>
980
- );
981
- }
278
+ if (!component.$connected) return <div className="p-8 text-center text-gray-500">Conectando...</div>
982
279
 
983
280
  return (
984
- <div className="bg-white border border-gray-200 rounded-lg shadow-sm p-6 m-4 relative">
985
- <div className="flex items-center justify-between mb-4">
986
- <h2 className="text-2xl font-bold text-gray-800">${componentName} Live Component</h2>
987
- <span className={
988
- \`px-2 py-1 rounded-full text-xs font-medium \${
989
- connected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
990
- }\`
991
- }>
992
- {connected ? '🟢 Connected' : '🔴 Disconnected'}
993
- </span>
994
- </div>
995
-
281
+ <div className="p-6 bg-white rounded-xl shadow-sm border">
282
+ <h2 className="text-xl font-bold mb-4">${name}</h2>
996
283
  <div className="space-y-4">
997
- <div>
998
- <p className="text-gray-600 mb-2">Server message:</p>
999
- <p className="text-lg font-semibold text-blue-600">{state.message}</p>
1000
- </div>
1001
-
1002
- <div>
1003
- <p className="text-gray-600 mb-2">Counter: <span className="font-bold text-2xl">{state.count}</span></p>
1004
- </div>
1005
-
1006
- <div className="flex gap-2 flex-wrap">
1007
- <button
1008
- onClick={() => call('updateMessage', { message: 'Hello from the client!' })}
1009
- disabled={loading}
1010
- className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
1011
- >
1012
- 📝 Update Message
1013
- </button>
1014
-
1015
- <button
1016
- onClick={() => call('incrementCounter')}
1017
- disabled={loading}
1018
- className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
1019
- >
1020
- ➕ Increment
1021
- </button>
1022
-
1023
- <button
1024
- onClick={() => call('resetData')}
1025
- disabled={loading}
1026
- className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed"
1027
- >
1028
- 🔄 Reset
1029
- </button>
1030
-
1031
- <button
1032
- onClick={async () => {
1033
- const result = await call('getData');
1034
- console.log('Component data:', result);
1035
- alert('Data logged to console');
1036
- }}
1037
- disabled={loading}
1038
- className="px-4 py-2 bg-purple-500 text-white rounded-lg hover:bg-purple-600 disabled:opacity-50 disabled:cursor-not-allowed"
1039
- >
1040
- 📊 Get Data
1041
- </button>
284
+ <p className="text-lg text-blue-600">{component.message}</p>
285
+ <p className="text-gray-600">Count: <span className="font-bold text-2xl">{component.count}</span></p>
286
+ <div className="flex gap-2">
287
+ <button onClick={() => component.updateMessage({ message: 'Hello!' })} className="px-4 py-2 bg-blue-500 text-white rounded-lg">Update</button>
288
+ <button onClick={() => component.increment()} className="px-4 py-2 bg-green-500 text-white rounded-lg">+1</button>
289
+ <button onClick={() => component.reset()} className="px-4 py-2 bg-gray-500 text-white rounded-lg">Reset</button>
1042
290
  </div>
1043
291
  </div>
1044
-
1045
- <div className="mt-4 text-xs text-gray-500 text-center">
1046
- Last updated: {new Date(state.lastUpdated).toLocaleTimeString()}
1047
- </div>
1048
-
1049
- {loading && (
1050
- <div className="absolute inset-0 bg-white bg-opacity-75 flex items-center justify-center rounded-lg">
1051
- <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
1052
- </div>
1053
- )}
1054
292
  </div>
1055
- );
1056
- }`;
293
+ )
294
+ }
295
+ `;
1057
296
  }
1058
297
  };
1059
298
 
1060
299
  export const createLiveComponentCommand: CliCommand = {
1061
- name: "make:component",
1062
- description: "Create a new Live Component with server and client files",
300
+ name: "make:live",
301
+ description: "Create a new Live Component",
1063
302
  category: "Live Components",
1064
- aliases: ["make:live", "create:component", "create:live-component"],
1065
- usage: "flux make:component <ComponentName> [options]",
303
+ aliases: ["make:component", "create:live"],
304
+ usage: "flux make:live <Name> [options]",
1066
305
  examples: [
1067
- "flux make:component UserProfile # Basic component",
1068
- "flux make:component TodoCounter --type=counter # Counter component",
1069
- "flux make:component ContactForm --type=form # Form component",
1070
- "flux make:component LiveChat --type=chat # Chat component",
1071
- "flux make:component ServerOnly --no-client # Server-only component",
1072
- "flux make:component MultiUser --room=lobby # Component with room support"
1073
- ],
1074
- arguments: [
1075
- {
1076
- name: "ComponentName",
1077
- description: "The name of the component in PascalCase (e.g., UserProfile, TodoCounter)",
1078
- required: true,
1079
- type: "string"
1080
- },
306
+ "flux make:live Counter --type=counter",
307
+ "flux make:live ContactForm --type=form",
308
+ "flux make:live Chat --type=chat",
309
+ "flux make:live MyComponent"
1081
310
  ],
311
+ arguments: [{ name: "Name", description: "Component name in PascalCase", required: true, type: "string" }],
1082
312
  options: [
1083
- {
1084
- name: "type",
1085
- short: "t",
1086
- description: "Type of component template to generate",
1087
- type: "string",
1088
- default: "basic",
1089
- choices: ["basic", "counter", "form", "chat"]
1090
- },
1091
- {
1092
- name: "no-client",
1093
- description: "Generate only server component (no client file)",
1094
- type: "boolean"
1095
- },
1096
- {
1097
- name: "room",
1098
- short: "r",
1099
- description: "Default room name for multi-user features",
1100
- type: "string"
1101
- },
1102
- {
1103
- name: "force",
1104
- short: "f",
1105
- description: "Overwrite existing files if they exist",
1106
- type: "boolean"
1107
- }
313
+ { name: "type", short: "t", description: "Template type", type: "string", default: "basic", choices: ["basic", "counter", "form", "chat"] },
314
+ { name: "no-client", description: "Server only", type: "boolean" },
315
+ { name: "room", short: "r", description: "Default room", type: "string" },
316
+ { name: "force", short: "f", description: "Overwrite", type: "boolean" }
1108
317
  ],
1109
318
  handler: async (args, options, context) => {
1110
- const [componentName] = args;
319
+ const [name] = args;
1111
320
  const { type = 'basic', 'no-client': noClient, room, force } = options;
1112
321
 
1113
- // Validation
1114
- if (!componentName || !/^[A-Z][a-zA-Z0-9]*$/.test(componentName)) {
1115
- context.logger.error("❌ Invalid component name. It must be in PascalCase (e.g., UserProfile, TodoCounter).");
1116
- context.logger.info("Examples: UserProfile, TodoCounter, ContactForm, LiveChat");
322
+ if (!name || !/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
323
+ context.logger.error("❌ Nome inválido. Use PascalCase (ex: MeuComponente)");
1117
324
  return;
1118
325
  }
1119
326
 
1120
- if (!['basic', 'counter', 'form', 'chat'].includes(type)) {
1121
- context.logger.error(`❌ Invalid component type: ${type}`);
1122
- context.logger.info("Available types: basic, counter, form, chat");
1123
- return;
1124
- }
1125
-
1126
- // File paths
1127
- const serverFilePath = path.join(context.workingDir, "app", "server", "live", `${componentName}Component.ts`);
1128
- const clientFilePath = path.join(context.workingDir, "app", "client", "src", "components", `${componentName}.tsx`);
327
+ const serverPath = path.join(context.workingDir, "app", "server", "live", `${name}.ts`);
328
+ const clientPath = path.join(context.workingDir, "app", "client", "src", "live", `${name}.tsx`);
1129
329
 
1130
330
  try {
1131
- // Check if files exist (unless force flag is used)
1132
331
  if (!force) {
1133
- const serverExists = await fs.access(serverFilePath).then(() => true).catch(() => false);
1134
- const clientExists = !noClient && await fs.access(clientFilePath).then(() => true).catch(() => false);
1135
-
332
+ const serverExists = await fs.access(serverPath).then(() => true).catch(() => false);
333
+ const clientExists = !noClient && await fs.access(clientPath).then(() => true).catch(() => false);
1136
334
  if (serverExists || clientExists) {
1137
- context.logger.error(`❌ Component files already exist. Use --force to overwrite.`);
1138
- if (serverExists) context.logger.info(` Server: ${serverFilePath}`);
1139
- if (clientExists) context.logger.info(` Client: ${clientFilePath}`);
335
+ context.logger.error("❌ Arquivos existem. Use --force para sobrescrever.");
1140
336
  return;
1141
337
  }
1142
338
  }
1143
339
 
1144
- // Ensure directories exist
1145
- await fs.mkdir(path.dirname(serverFilePath), { recursive: true });
1146
- if (!noClient) {
1147
- await fs.mkdir(path.dirname(clientFilePath), { recursive: true });
1148
- }
340
+ await fs.mkdir(path.dirname(serverPath), { recursive: true });
341
+ if (!noClient) await fs.mkdir(path.dirname(clientPath), { recursive: true });
1149
342
 
1150
- // Generate server component
1151
- context.logger.info(`🔥 Creating server component: ${componentName}Component.ts`);
1152
- const serverTemplate = getServerTemplate(componentName, type, room);
1153
- await fs.writeFile(serverFilePath, serverTemplate);
343
+ context.logger.info(`🔥 Criando ${name}...`);
1154
344
 
1155
- // Generate client component (unless --no-client)
1156
- if (!noClient) {
1157
- context.logger.info(`⚛️ Creating client component: ${componentName}.tsx`);
1158
- const clientTemplate = getClientTemplate(componentName, type, room);
1159
- await fs.writeFile(clientFilePath, clientTemplate);
1160
- }
345
+ await fs.writeFile(serverPath, getServerTemplate(name, type, room, !noClient));
346
+ context.logger.info(` ✅ Server: app/server/live/${name}.ts`);
1161
347
 
1162
- // Success message
1163
- context.logger.info(`✅ Successfully created '${componentName}' live component!`);
1164
- context.logger.info("");
1165
- context.logger.info("📁 Files created:");
1166
- context.logger.info(` 🔥 Server: app/server/live/${componentName}Component.ts`);
1167
- if (!noClient) {
1168
- context.logger.info(` ⚛️ Client: app/client/src/components/${componentName}.tsx`);
1169
- }
1170
-
1171
- context.logger.info("");
1172
- context.logger.info("🚀 Next steps:");
1173
- context.logger.info(" 1. Start dev server: bun run dev");
1174
348
  if (!noClient) {
1175
- context.logger.info(` 2. Import component in your App.tsx:`);
1176
- context.logger.info(` import { ${componentName} } from './components/${componentName}'`);
1177
- context.logger.info(` 3. Add component to your JSX: <${componentName} />`);
1178
- }
1179
-
1180
- if (room) {
1181
- context.logger.info(` 4. Component supports multi-user features with room: ${room}`);
1182
- }
1183
-
1184
- if (type !== 'basic') {
1185
- context.logger.info(` 5. Template type '${type}' includes specialized functionality`);
349
+ await fs.writeFile(clientPath, getClientTemplate(name, type));
350
+ context.logger.info(` Client: app/client/src/live/${name}.tsx`);
1186
351
  }
1187
352
 
1188
353
  context.logger.info("");
1189
- context.logger.info("📚 Import guide:");
1190
- context.logger.info(" # Use the fluxstack library (recommended):");
1191
- context.logger.info(" import { useHybridLiveComponent } from 'fluxstack';");
354
+ context.logger.info("🚀 Uso:");
355
+ context.logger.info(` import { ${name}Demo } from './live/${name}'`);
356
+ context.logger.info(` <${name}Demo />`);
1192
357
  context.logger.info("");
1193
- context.logger.info(" # Alternative - Direct core import:");
1194
- context.logger.info(" import { useHybridLiveComponent } from '@/core/client';");
358
+ context.logger.info("📖 API:");
359
+ context.logger.info(` const x = Live.use(${name}) // usa defaultState do backend`);
360
+ context.logger.info(` const x = Live.use(${name}, { ... }) // com override parcial`);
361
+ context.logger.info(` x.property // estado`);
362
+ context.logger.info(` x.action() // ação`);
363
+ context.logger.info(` x.$connected // status`);
1195
364
 
1196
365
  } catch (error) {
1197
- context.logger.error(`❌ Failed to create component files: ${error instanceof Error ? error.message : String(error)}`);
1198
- throw error;
366
+ context.logger.error(`❌ Erro: ${error instanceof Error ? error.message : String(error)}`);
1199
367
  }
1200
368
  },
1201
- };
369
+ };