create-fluxstack 1.0.13 → 1.0.15
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.
- package/.env.example +29 -29
- package/app/client/README.md +69 -69
- package/app/client/index.html +14 -13
- package/app/client/src/App.tsx +157 -524
- package/app/client/src/components/ErrorBoundary.tsx +107 -0
- package/app/client/src/components/ErrorDisplay.css +365 -0
- package/app/client/src/components/ErrorDisplay.tsx +258 -0
- package/app/client/src/components/FluxStackConfig.tsx +1321 -0
- package/app/client/src/components/HybridLiveCounter.tsx +140 -0
- package/app/client/src/components/LiveClock.tsx +286 -0
- package/app/client/src/components/MainLayout.tsx +390 -0
- package/app/client/src/components/SidebarNavigation.tsx +391 -0
- package/app/client/src/components/StateDemo.tsx +178 -0
- package/app/client/src/components/SystemMonitor.tsx +1038 -0
- package/app/client/src/components/Teste.tsx +104 -0
- package/app/client/src/components/UserProfile.tsx +809 -0
- package/app/client/src/hooks/useAuth.ts +39 -0
- package/app/client/src/hooks/useNotifications.ts +56 -0
- package/app/client/src/lib/eden-api.ts +189 -53
- package/app/client/src/lib/errors.ts +340 -0
- package/app/client/src/lib/hooks/useErrorHandler.ts +258 -0
- package/app/client/src/lib/index.ts +45 -0
- package/app/client/src/main.tsx +3 -2
- package/app/client/src/pages/ApiDocs.tsx +182 -0
- package/app/client/src/pages/Demo.tsx +174 -0
- package/app/client/src/pages/HybridLive.tsx +263 -0
- package/app/client/src/pages/Overview.tsx +155 -0
- package/app/client/src/store/README.md +43 -0
- package/app/client/src/store/index.ts +16 -0
- package/app/client/src/store/slices/uiSlice.ts +151 -0
- package/app/client/src/store/slices/userSlice.ts +161 -0
- package/app/client/src/test/README.md +257 -0
- package/app/client/src/test/setup.ts +70 -0
- package/app/client/src/test/types.ts +12 -0
- package/app/client/src/vite-env.d.ts +1 -1
- package/app/client/tsconfig.app.json +44 -43
- package/app/client/tsconfig.json +7 -7
- package/app/client/tsconfig.node.json +25 -25
- package/app/client/zustand-setup.md +65 -0
- package/app/server/controllers/users.controller.ts +68 -68
- package/app/server/index.ts +9 -1
- package/app/server/live/CounterComponent.ts +191 -0
- package/app/server/live/FluxStackConfig.ts +529 -0
- package/app/server/live/LiveClockComponent.ts +214 -0
- package/app/server/live/SidebarNavigation.ts +156 -0
- package/app/server/live/SystemMonitor.ts +594 -0
- package/app/server/live/SystemMonitorIntegration.ts +151 -0
- package/app/server/live/TesteComponent.ts +87 -0
- package/app/server/live/UserProfileComponent.ts +135 -0
- package/app/server/live/register-components.ts +28 -0
- package/app/server/middleware/auth.ts +136 -0
- package/app/server/middleware/errorHandling.ts +250 -0
- package/app/server/middleware/index.ts +10 -0
- package/app/server/middleware/rateLimit.ts +193 -0
- package/app/server/middleware/requestLogging.ts +215 -0
- package/app/server/middleware/validation.ts +270 -0
- package/app/server/routes/index.ts +14 -2
- package/app/server/routes/upload.ts +92 -0
- package/app/server/routes/users.routes.ts +2 -9
- package/app/server/services/NotificationService.ts +302 -0
- package/app/server/services/UserService.ts +222 -0
- package/app/server/services/index.ts +46 -0
- package/core/cli/commands/plugin-deps.ts +263 -0
- package/core/cli/generators/README.md +339 -0
- package/core/cli/generators/component.ts +770 -0
- package/core/cli/generators/controller.ts +299 -0
- package/core/cli/generators/index.ts +144 -0
- package/core/cli/generators/interactive.ts +228 -0
- package/core/cli/generators/prompts.ts +83 -0
- package/core/cli/generators/route.ts +513 -0
- package/core/cli/generators/service.ts +465 -0
- package/core/cli/generators/template-engine.ts +154 -0
- package/core/cli/generators/types.ts +71 -0
- package/core/cli/generators/utils.ts +192 -0
- package/core/cli/index.ts +69 -0
- package/core/cli/plugin-discovery.ts +16 -85
- package/core/client/fluxstack.ts +17 -0
- package/core/client/hooks/index.ts +7 -0
- package/core/client/hooks/state-validator.ts +130 -0
- package/core/client/hooks/useAuth.ts +49 -0
- package/core/client/hooks/useChunkedUpload.ts +258 -0
- package/core/client/hooks/useHybridLiveComponent.ts +967 -0
- package/core/client/hooks/useWebSocket.ts +373 -0
- package/core/client/index.ts +47 -0
- package/core/client/state/createStore.ts +193 -0
- package/core/client/state/index.ts +15 -0
- package/core/config/env-dynamic.ts +1 -1
- package/core/config/env.ts +2 -1
- package/core/config/runtime-config.ts +3 -3
- package/core/config/schema.ts +84 -49
- package/core/framework/server.ts +30 -0
- package/core/index.ts +25 -0
- package/core/live/ComponentRegistry.ts +399 -0
- package/core/live/types.ts +164 -0
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +1201 -0
- package/core/plugins/built-in/live-components/index.ts +27 -0
- package/core/plugins/built-in/logger/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +1 -1
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +1 -1
- package/core/plugins/built-in/vite/index.ts +1 -1
- package/core/plugins/dependency-manager.ts +384 -0
- package/core/plugins/index.ts +5 -1
- package/core/plugins/manager.ts +7 -3
- package/core/plugins/registry.ts +88 -10
- package/core/plugins/types.ts +11 -11
- package/core/server/framework.ts +43 -0
- package/core/server/index.ts +11 -1
- package/core/server/live/ComponentRegistry.ts +1017 -0
- package/core/server/live/FileUploadManager.ts +272 -0
- package/core/server/live/LiveComponentPerformanceMonitor.ts +930 -0
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +644 -0
- package/core/server/live/WebSocketConnectionManager.ts +688 -0
- package/core/server/live/websocket-plugin.ts +435 -0
- package/core/server/middleware/errorHandling.ts +141 -0
- package/core/server/middleware/index.ts +16 -0
- package/core/server/plugins/static-files-plugin.ts +232 -0
- package/core/server/services/BaseService.ts +95 -0
- package/core/server/services/ServiceContainer.ts +144 -0
- package/core/server/services/index.ts +9 -0
- package/core/templates/create-project.ts +196 -33
- package/core/testing/index.ts +10 -0
- package/core/testing/setup.ts +74 -0
- package/core/types/build.ts +38 -14
- package/core/types/types.ts +319 -0
- package/core/utils/env-runtime.ts +7 -0
- package/core/utils/errors/handlers.ts +264 -39
- package/core/utils/errors/index.ts +528 -18
- package/core/utils/errors/middleware.ts +114 -0
- package/core/utils/logger/formatters.ts +222 -0
- package/core/utils/logger/index.ts +167 -48
- package/core/utils/logger/middleware.ts +253 -0
- package/core/utils/logger/performance.ts +384 -0
- package/core/utils/logger/transports.ts +365 -0
- package/create-fluxstack.ts +296 -296
- package/fluxstack.config.ts +17 -1
- package/package-template.json +66 -66
- package/package.json +31 -6
- package/public/README.md +16 -0
- package/vite.config.ts +29 -14
- package/.claude/settings.local.json +0 -74
- package/.github/workflows/ci-build-tests.yml +0 -480
- package/.github/workflows/dependency-management.yml +0 -324
- package/.github/workflows/release-validation.yml +0 -355
- package/.kiro/specs/fluxstack-architecture-optimization/design.md +0 -700
- package/.kiro/specs/fluxstack-architecture-optimization/requirements.md +0 -127
- package/.kiro/specs/fluxstack-architecture-optimization/tasks.md +0 -330
- package/CLAUDE.md +0 -200
- package/Dockerfile +0 -58
- package/Dockerfile.backend +0 -52
- package/Dockerfile.frontend +0 -54
- package/README-Docker.md +0 -85
- package/ai-context/00-QUICK-START.md +0 -86
- package/ai-context/README.md +0 -88
- package/ai-context/development/eden-treaty-guide.md +0 -362
- package/ai-context/development/patterns.md +0 -382
- package/ai-context/development/plugins-guide.md +0 -572
- package/ai-context/examples/crud-complete.md +0 -626
- package/ai-context/project/architecture.md +0 -399
- package/ai-context/project/overview.md +0 -213
- package/ai-context/recent-changes/eden-treaty-refactor.md +0 -281
- package/ai-context/recent-changes/type-inference-fix.md +0 -223
- package/ai-context/reference/environment-vars.md +0 -384
- package/ai-context/reference/troubleshooting.md +0 -407
- package/app/client/src/components/TestPage.tsx +0 -453
- package/bun.lock +0 -1063
- package/bunfig.toml +0 -16
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/config/__tests__/config-loader.test.ts +0 -554
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/create-test-app.ts +0 -156
- package/docker-compose.microservices.yml +0 -75
- package/docker-compose.simple.yml +0 -57
- package/docker-compose.yml +0 -71
- package/eslint.config.js +0 -23
- package/flux-cli.ts +0 -214
- package/nginx-lb.conf +0 -37
- package/publish.sh +0 -63
- package/run-clean.ts +0 -26
- package/run-env-tests.ts +0 -313
- package/tailwind.config.js +0 -34
- package/tests/__mocks__/api.ts +0 -56
- package/tests/fixtures/users.ts +0 -69
- package/tests/integration/api/users.routes.test.ts +0 -221
- package/tests/setup.ts +0 -29
- package/tests/unit/app/client/App-simple.test.tsx +0 -56
- package/tests/unit/app/client/App.test.tsx.skip +0 -237
- package/tests/unit/app/client/eden-api.test.ts +0 -186
- package/tests/unit/app/client/simple.test.tsx +0 -23
- package/tests/unit/app/controllers/users.controller.test.ts +0 -150
- package/tests/unit/core/create-project.test.ts.skip +0 -95
- package/tests/unit/core/framework.test.ts +0 -144
- package/tests/unit/core/plugins/logger.test.ts.skip +0 -268
- package/tests/unit/core/plugins/vite.test.ts.disabled +0 -188
- package/tests/utils/test-helpers.ts +0 -61
- package/vitest.config.ts +0 -50
- package/workspace.json +0 -6
|
@@ -1,626 +0,0 @@
|
|
|
1
|
-
# 💡 Exemplo CRUD Completo - FluxStack + Eden Treaty
|
|
2
|
-
|
|
3
|
-
> **Exemplo prático**: CRUD de usuários com type safety automática end-to-end
|
|
4
|
-
|
|
5
|
-
## 🎯 **Visão Geral do Exemplo**
|
|
6
|
-
|
|
7
|
-
Este exemplo mostra como implementar um CRUD completo no FluxStack, demonstrando:
|
|
8
|
-
- **Types compartilhados** entre client e server
|
|
9
|
-
- **Controllers** com lógica de negócio
|
|
10
|
-
- **Routes** com response schemas
|
|
11
|
-
- **Frontend React** com Eden Treaty nativo
|
|
12
|
-
- **Error handling** elegante
|
|
13
|
-
- **Type safety** automática
|
|
14
|
-
|
|
15
|
-
## 📁 **Estrutura do Exemplo**
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
app/
|
|
19
|
-
├── shared/types/index.ts # Types compartilhados
|
|
20
|
-
├── server/
|
|
21
|
-
│ ├── controllers/users.controller.ts # Lógica de negócio
|
|
22
|
-
│ └── routes/users.routes.ts # Endpoints da API
|
|
23
|
-
└── client/src/
|
|
24
|
-
├── lib/eden-api.ts # Cliente Eden Treaty
|
|
25
|
-
├── components/UserList.tsx # Lista de usuários
|
|
26
|
-
└── hooks/useUsers.ts # Hook personalizado
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## 🔧 **Implementação Passo a Passo**
|
|
30
|
-
|
|
31
|
-
### **1. Types Compartilhados (app/shared/types/index.ts)**
|
|
32
|
-
```typescript
|
|
33
|
-
// Entidades principais
|
|
34
|
-
export interface User {
|
|
35
|
-
id: number
|
|
36
|
-
name: string
|
|
37
|
-
email: string
|
|
38
|
-
createdAt: Date
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Request types
|
|
42
|
-
export interface CreateUserRequest {
|
|
43
|
-
name: string
|
|
44
|
-
email: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface UpdateUserRequest {
|
|
48
|
-
name?: string
|
|
49
|
-
email?: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Response types
|
|
53
|
-
export interface UserResponse {
|
|
54
|
-
success: boolean
|
|
55
|
-
user?: User
|
|
56
|
-
message?: string
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface UsersListResponse {
|
|
60
|
-
users: User[]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Error types
|
|
64
|
-
export interface APIError {
|
|
65
|
-
message: string
|
|
66
|
-
status: number
|
|
67
|
-
code?: string
|
|
68
|
-
details?: any
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### **2. Controller (app/server/controllers/users.controller.ts)**
|
|
73
|
-
```typescript
|
|
74
|
-
import type { User, CreateUserRequest, UpdateUserRequest, UserResponse } from '@/shared/types'
|
|
75
|
-
|
|
76
|
-
// Simulando database em memória
|
|
77
|
-
let users: User[] = [
|
|
78
|
-
{ id: 1, name: "João Silva", email: "joao@example.com", createdAt: new Date() },
|
|
79
|
-
{ id: 2, name: "Maria Santos", email: "maria@example.com", createdAt: new Date() }
|
|
80
|
-
]
|
|
81
|
-
|
|
82
|
-
export class UsersController {
|
|
83
|
-
// GET /users - Listar todos
|
|
84
|
-
static async getUsers() {
|
|
85
|
-
return { users }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// GET /users/:id - Buscar por ID
|
|
89
|
-
static async getUserById(id: number) {
|
|
90
|
-
const user = users.find(u => u.id === id)
|
|
91
|
-
return user ? { user } : null
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// POST /users - Criar novo
|
|
95
|
-
static async createUser(userData: CreateUserRequest): Promise<UserResponse> {
|
|
96
|
-
// Validação de email único
|
|
97
|
-
const existingUser = users.find(u => u.email === userData.email)
|
|
98
|
-
|
|
99
|
-
if (existingUser) {
|
|
100
|
-
return {
|
|
101
|
-
success: false,
|
|
102
|
-
message: "Email já está em uso"
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Criar novo usuário
|
|
107
|
-
const newUser: User = {
|
|
108
|
-
id: Date.now(), // Simples ID generator
|
|
109
|
-
name: userData.name,
|
|
110
|
-
email: userData.email,
|
|
111
|
-
createdAt: new Date()
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
users.push(newUser)
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
success: true,
|
|
118
|
-
user: newUser,
|
|
119
|
-
message: "Usuário criado com sucesso"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// PUT /users/:id - Atualizar
|
|
124
|
-
static async updateUser(id: number, userData: UpdateUserRequest): Promise<UserResponse> {
|
|
125
|
-
const userIndex = users.findIndex(u => u.id === id)
|
|
126
|
-
|
|
127
|
-
if (userIndex === -1) {
|
|
128
|
-
return {
|
|
129
|
-
success: false,
|
|
130
|
-
message: "Usuário não encontrado"
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Verificar email único (se alterando)
|
|
135
|
-
if (userData.email) {
|
|
136
|
-
const existingUser = users.find(u => u.email === userData.email && u.id !== id)
|
|
137
|
-
if (existingUser) {
|
|
138
|
-
return {
|
|
139
|
-
success: false,
|
|
140
|
-
message: "Email já está em uso"
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Atualizar usuário
|
|
146
|
-
users[userIndex] = {
|
|
147
|
-
...users[userIndex],
|
|
148
|
-
...userData
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
success: true,
|
|
153
|
-
user: users[userIndex],
|
|
154
|
-
message: "Usuário atualizado com sucesso"
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// DELETE /users/:id - Deletar
|
|
159
|
-
static async deleteUser(id: number): Promise<UserResponse> {
|
|
160
|
-
const userIndex = users.findIndex(u => u.id === id)
|
|
161
|
-
|
|
162
|
-
if (userIndex === -1) {
|
|
163
|
-
return {
|
|
164
|
-
success: false,
|
|
165
|
-
message: "Usuário não encontrado"
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const deletedUser = users.splice(userIndex, 1)[0]
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
success: true,
|
|
173
|
-
user: deletedUser,
|
|
174
|
-
message: "Usuário deletado com sucesso"
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Utility para reset (testing)
|
|
179
|
-
static resetUsers() {
|
|
180
|
-
users.splice(0, users.length)
|
|
181
|
-
users.push(
|
|
182
|
-
{ id: 1, name: "João Silva", email: "joao@example.com", createdAt: new Date() },
|
|
183
|
-
{ id: 2, name: "Maria Santos", email: "maria@example.com", createdAt: new Date() }
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### **3. Routes com Response Schemas (app/server/routes/users.routes.ts)**
|
|
190
|
-
```typescript
|
|
191
|
-
import { Elysia, t } from "elysia"
|
|
192
|
-
import { UsersController } from "../controllers/users.controller"
|
|
193
|
-
|
|
194
|
-
// Schemas TypeBox para validação e documentação
|
|
195
|
-
const UserSchema = t.Object({
|
|
196
|
-
id: t.Number(),
|
|
197
|
-
name: t.String(),
|
|
198
|
-
email: t.String(),
|
|
199
|
-
createdAt: t.Date()
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
const UserResponseSchema = t.Object({
|
|
203
|
-
success: t.Boolean(),
|
|
204
|
-
user: t.Optional(UserSchema),
|
|
205
|
-
message: t.Optional(t.String())
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
export const usersRoutes = new Elysia({ prefix: "/users" })
|
|
209
|
-
|
|
210
|
-
// GET /users - Listar todos
|
|
211
|
-
.get("/", () => UsersController.getUsers(), {
|
|
212
|
-
response: t.Object({
|
|
213
|
-
users: t.Array(UserSchema)
|
|
214
|
-
}),
|
|
215
|
-
detail: {
|
|
216
|
-
tags: ['Users'],
|
|
217
|
-
summary: 'List Users',
|
|
218
|
-
description: 'Retrieve a list of all users in the system'
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
// GET /users/:id - Buscar por ID
|
|
223
|
-
.get("/:id", async ({ params: { id }, set }) => {
|
|
224
|
-
const userId = parseInt(id)
|
|
225
|
-
const result = await UsersController.getUserById(userId)
|
|
226
|
-
|
|
227
|
-
if (!result) {
|
|
228
|
-
set.status = 404
|
|
229
|
-
return { error: "Usuário não encontrado" }
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return result
|
|
233
|
-
}, {
|
|
234
|
-
params: t.Object({
|
|
235
|
-
id: t.String()
|
|
236
|
-
}),
|
|
237
|
-
response: t.Object({
|
|
238
|
-
user: UserSchema
|
|
239
|
-
}),
|
|
240
|
-
detail: {
|
|
241
|
-
tags: ['Users'],
|
|
242
|
-
summary: 'Get User by ID',
|
|
243
|
-
description: 'Retrieve a specific user by their ID'
|
|
244
|
-
}
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
// POST /users - Criar novo
|
|
248
|
-
.post("/", async ({ body, set }) => {
|
|
249
|
-
try {
|
|
250
|
-
return await UsersController.createUser(body)
|
|
251
|
-
} catch (error) {
|
|
252
|
-
set.status = 400
|
|
253
|
-
return {
|
|
254
|
-
success: false,
|
|
255
|
-
error: "Dados inválidos",
|
|
256
|
-
details: error instanceof Error ? error.message : 'Unknown error'
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}, {
|
|
260
|
-
body: t.Object({
|
|
261
|
-
name: t.String({ minLength: 2 }),
|
|
262
|
-
email: t.String({ format: "email" })
|
|
263
|
-
}),
|
|
264
|
-
response: UserResponseSchema,
|
|
265
|
-
detail: {
|
|
266
|
-
tags: ['Users'],
|
|
267
|
-
summary: 'Create User',
|
|
268
|
-
description: 'Create a new user with name and email'
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
// PUT /users/:id - Atualizar
|
|
273
|
-
.put("/:id", async ({ params: { id }, body, set }) => {
|
|
274
|
-
try {
|
|
275
|
-
const userId = parseInt(id)
|
|
276
|
-
return await UsersController.updateUser(userId, body)
|
|
277
|
-
} catch (error) {
|
|
278
|
-
set.status = 400
|
|
279
|
-
return {
|
|
280
|
-
success: false,
|
|
281
|
-
error: "Dados inválidos",
|
|
282
|
-
details: error instanceof Error ? error.message : 'Unknown error'
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}, {
|
|
286
|
-
params: t.Object({
|
|
287
|
-
id: t.String()
|
|
288
|
-
}),
|
|
289
|
-
body: t.Object({
|
|
290
|
-
name: t.Optional(t.String({ minLength: 2 })),
|
|
291
|
-
email: t.Optional(t.String({ format: "email" }))
|
|
292
|
-
}),
|
|
293
|
-
response: UserResponseSchema,
|
|
294
|
-
detail: {
|
|
295
|
-
tags: ['Users'],
|
|
296
|
-
summary: 'Update User',
|
|
297
|
-
description: 'Update an existing user'
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
// DELETE /users/:id - Deletar
|
|
302
|
-
.delete("/:id", async ({ params: { id } }) => {
|
|
303
|
-
const userId = parseInt(id)
|
|
304
|
-
return UsersController.deleteUser(userId)
|
|
305
|
-
}, {
|
|
306
|
-
params: t.Object({
|
|
307
|
-
id: t.String()
|
|
308
|
-
}),
|
|
309
|
-
response: UserResponseSchema,
|
|
310
|
-
detail: {
|
|
311
|
-
tags: ['Users'],
|
|
312
|
-
summary: 'Delete User',
|
|
313
|
-
description: 'Delete a user by their ID'
|
|
314
|
-
}
|
|
315
|
-
})
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### **4. Hook Personalizado (app/client/src/hooks/useUsers.ts)**
|
|
319
|
-
```typescript
|
|
320
|
-
import { useState, useEffect } from 'react'
|
|
321
|
-
import { api } from '../lib/eden-api'
|
|
322
|
-
import type { User, CreateUserRequest, UpdateUserRequest } from '@/shared/types'
|
|
323
|
-
|
|
324
|
-
export function useUsers() {
|
|
325
|
-
const [users, setUsers] = useState<User[]>([])
|
|
326
|
-
const [loading, setLoading] = useState(false)
|
|
327
|
-
const [error, setError] = useState<string | null>(null)
|
|
328
|
-
|
|
329
|
-
// Carregar usuários
|
|
330
|
-
const loadUsers = async () => {
|
|
331
|
-
setLoading(true)
|
|
332
|
-
setError(null)
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
const { data, error } = await api.users.get()
|
|
336
|
-
|
|
337
|
-
if (error) {
|
|
338
|
-
setError(`Erro ao carregar usuários: ${error.status}`)
|
|
339
|
-
return
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ✨ Eden Treaty infere: data = { users: User[] }
|
|
343
|
-
setUsers(data.users)
|
|
344
|
-
} catch (err) {
|
|
345
|
-
setError('Erro de rede ao carregar usuários')
|
|
346
|
-
} finally {
|
|
347
|
-
setLoading(false)
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Criar usuário
|
|
352
|
-
const createUser = async (userData: CreateUserRequest): Promise<boolean> => {
|
|
353
|
-
try {
|
|
354
|
-
const { data, error } = await api.users.post(userData)
|
|
355
|
-
|
|
356
|
-
if (error) {
|
|
357
|
-
setError(`Erro ao criar usuário: ${error.status}`)
|
|
358
|
-
return false
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// ✨ Eden Treaty infere: data = UserResponse
|
|
362
|
-
if (data.success && data.user) {
|
|
363
|
-
setUsers(prev => [...prev, data.user!])
|
|
364
|
-
return true
|
|
365
|
-
} else {
|
|
366
|
-
setError(data.message || 'Erro ao criar usuário')
|
|
367
|
-
return false
|
|
368
|
-
}
|
|
369
|
-
} catch (err) {
|
|
370
|
-
setError('Erro de rede ao criar usuário')
|
|
371
|
-
return false
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Atualizar usuário
|
|
376
|
-
const updateUser = async (id: number, userData: UpdateUserRequest): Promise<boolean> => {
|
|
377
|
-
try {
|
|
378
|
-
const { data, error } = await api.users({ id }).put(userData)
|
|
379
|
-
|
|
380
|
-
if (error) {
|
|
381
|
-
setError(`Erro ao atualizar usuário: ${error.status}`)
|
|
382
|
-
return false
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// ✨ Eden Treaty infere tipos automaticamente
|
|
386
|
-
if (data.success && data.user) {
|
|
387
|
-
setUsers(prev => prev.map(u => u.id === id ? data.user! : u))
|
|
388
|
-
return true
|
|
389
|
-
} else {
|
|
390
|
-
setError(data.message || 'Erro ao atualizar usuário')
|
|
391
|
-
return false
|
|
392
|
-
}
|
|
393
|
-
} catch (err) {
|
|
394
|
-
setError('Erro de rede ao atualizar usuário')
|
|
395
|
-
return false
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Deletar usuário
|
|
400
|
-
const deleteUser = async (id: number): Promise<boolean> => {
|
|
401
|
-
try {
|
|
402
|
-
const { data, error } = await api.users({ id }).delete()
|
|
403
|
-
|
|
404
|
-
if (error) {
|
|
405
|
-
setError(`Erro ao deletar usuário: ${error.status}`)
|
|
406
|
-
return false
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ✨ Type safety automática
|
|
410
|
-
if (data.success) {
|
|
411
|
-
setUsers(prev => prev.filter(u => u.id !== id))
|
|
412
|
-
return true
|
|
413
|
-
} else {
|
|
414
|
-
setError(data.message || 'Erro ao deletar usuário')
|
|
415
|
-
return false
|
|
416
|
-
}
|
|
417
|
-
} catch (err) {
|
|
418
|
-
setError('Erro de rede ao deletar usuário')
|
|
419
|
-
return false
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Carregar na inicialização
|
|
424
|
-
useEffect(() => {
|
|
425
|
-
loadUsers()
|
|
426
|
-
}, [])
|
|
427
|
-
|
|
428
|
-
return {
|
|
429
|
-
users,
|
|
430
|
-
loading,
|
|
431
|
-
error,
|
|
432
|
-
loadUsers,
|
|
433
|
-
createUser,
|
|
434
|
-
updateUser,
|
|
435
|
-
deleteUser,
|
|
436
|
-
clearError: () => setError(null)
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### **5. Componente React (app/client/src/components/UserList.tsx)**
|
|
442
|
-
```typescript
|
|
443
|
-
import React, { useState } from 'react'
|
|
444
|
-
import { useUsers } from '../hooks/useUsers'
|
|
445
|
-
import type { CreateUserRequest } from '@/shared/types'
|
|
446
|
-
|
|
447
|
-
export function UserList() {
|
|
448
|
-
const { users, loading, error, createUser, updateUser, deleteUser, clearError } = useUsers()
|
|
449
|
-
const [showForm, setShowForm] = useState(false)
|
|
450
|
-
const [formData, setFormData] = useState<CreateUserRequest>({ name: '', email: '' })
|
|
451
|
-
const [editingId, setEditingId] = useState<number | null>(null)
|
|
452
|
-
|
|
453
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
454
|
-
e.preventDefault()
|
|
455
|
-
|
|
456
|
-
if (!formData.name.trim() || !formData.email.trim()) return
|
|
457
|
-
|
|
458
|
-
const success = editingId
|
|
459
|
-
? await updateUser(editingId, formData)
|
|
460
|
-
: await createUser(formData)
|
|
461
|
-
|
|
462
|
-
if (success) {
|
|
463
|
-
setFormData({ name: '', email: '' })
|
|
464
|
-
setShowForm(false)
|
|
465
|
-
setEditingId(null)
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const handleEdit = (user: User) => {
|
|
470
|
-
setFormData({ name: user.name, email: user.email })
|
|
471
|
-
setEditingId(user.id)
|
|
472
|
-
setShowForm(true)
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const handleDelete = async (user: User) => {
|
|
476
|
-
if (confirm(`Tem certeza que deseja deletar ${user.name}?`)) {
|
|
477
|
-
await deleteUser(user.id)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return (
|
|
482
|
-
<div className="user-list">
|
|
483
|
-
<h2>Usuários</h2>
|
|
484
|
-
|
|
485
|
-
{error && (
|
|
486
|
-
<div className="error">
|
|
487
|
-
{error}
|
|
488
|
-
<button onClick={clearError}>✕</button>
|
|
489
|
-
</div>
|
|
490
|
-
)}
|
|
491
|
-
|
|
492
|
-
<button
|
|
493
|
-
onClick={() => setShowForm(!showForm)}
|
|
494
|
-
className="btn-primary"
|
|
495
|
-
>
|
|
496
|
-
{showForm ? 'Cancelar' : 'Novo Usuário'}
|
|
497
|
-
</button>
|
|
498
|
-
|
|
499
|
-
{showForm && (
|
|
500
|
-
<form onSubmit={handleSubmit} className="user-form">
|
|
501
|
-
<input
|
|
502
|
-
type="text"
|
|
503
|
-
placeholder="Nome"
|
|
504
|
-
value={formData.name}
|
|
505
|
-
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
|
506
|
-
required
|
|
507
|
-
/>
|
|
508
|
-
<input
|
|
509
|
-
type="email"
|
|
510
|
-
placeholder="Email"
|
|
511
|
-
value={formData.email}
|
|
512
|
-
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
|
|
513
|
-
required
|
|
514
|
-
/>
|
|
515
|
-
<button type="submit" disabled={loading}>
|
|
516
|
-
{editingId ? 'Atualizar' : 'Criar'}
|
|
517
|
-
</button>
|
|
518
|
-
</form>
|
|
519
|
-
)}
|
|
520
|
-
|
|
521
|
-
{loading && <div className="loading">Carregando...</div>}
|
|
522
|
-
|
|
523
|
-
<div className="users-grid">
|
|
524
|
-
{users.map(user => (
|
|
525
|
-
<div key={user.id} className="user-card">
|
|
526
|
-
<h3>{user.name}</h3>
|
|
527
|
-
<p>{user.email}</p>
|
|
528
|
-
<small>Criado em: {new Date(user.createdAt).toLocaleDateString()}</small>
|
|
529
|
-
<div className="user-actions">
|
|
530
|
-
<button onClick={() => handleEdit(user)}>Editar</button>
|
|
531
|
-
<button onClick={() => handleDelete(user)}>Deletar</button>
|
|
532
|
-
</div>
|
|
533
|
-
</div>
|
|
534
|
-
))}
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
)
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
## 🧪 **Teste do CRUD**
|
|
542
|
-
|
|
543
|
-
### **Teste Simples**
|
|
544
|
-
```bash
|
|
545
|
-
# 1. Iniciar servidor
|
|
546
|
-
bun run dev
|
|
547
|
-
|
|
548
|
-
# 2. Testar APIs
|
|
549
|
-
curl http://localhost:3000/api/users
|
|
550
|
-
curl -X POST http://localhost:3000/api/users \
|
|
551
|
-
-H "Content-Type: application/json" \
|
|
552
|
-
-d '{"name":"Test User","email":"test@example.com"}'
|
|
553
|
-
|
|
554
|
-
# 3. Frontend
|
|
555
|
-
# Acessar http://localhost:5173
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
### **Teste com Vitest**
|
|
559
|
-
```typescript
|
|
560
|
-
// tests/users-crud.test.ts
|
|
561
|
-
import { describe, it, expect, beforeEach } from 'vitest'
|
|
562
|
-
import { api } from '@/client/lib/eden-api'
|
|
563
|
-
|
|
564
|
-
describe('Users CRUD', () => {
|
|
565
|
-
beforeEach(async () => {
|
|
566
|
-
// Reset para estado inicial
|
|
567
|
-
await api.users.reset.post()
|
|
568
|
-
})
|
|
569
|
-
|
|
570
|
-
it('should create, read, update, delete user', async () => {
|
|
571
|
-
// CREATE
|
|
572
|
-
const { data: createResult, error: createError } = await api.users.post({
|
|
573
|
-
name: "João Test",
|
|
574
|
-
email: "joao@test.com"
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
expect(createError).toBeUndefined()
|
|
578
|
-
expect(createResult.success).toBe(true)
|
|
579
|
-
expect(createResult.user).toBeDefined()
|
|
580
|
-
|
|
581
|
-
const userId = createResult.user!.id
|
|
582
|
-
|
|
583
|
-
// READ
|
|
584
|
-
const { data: getResult, error: getError } = await api.users({ id: userId }).get()
|
|
585
|
-
expect(getError).toBeUndefined()
|
|
586
|
-
expect(getResult.user.name).toBe("João Test")
|
|
587
|
-
|
|
588
|
-
// UPDATE
|
|
589
|
-
const { data: updateResult, error: updateError } = await api.users({ id: userId }).put({
|
|
590
|
-
name: "João Updated"
|
|
591
|
-
})
|
|
592
|
-
expect(updateError).toBeUndefined()
|
|
593
|
-
expect(updateResult.success).toBe(true)
|
|
594
|
-
expect(updateResult.user!.name).toBe("João Updated")
|
|
595
|
-
|
|
596
|
-
// DELETE
|
|
597
|
-
const { data: deleteResult, error: deleteError } = await api.users({ id: userId }).delete()
|
|
598
|
-
expect(deleteError).toBeUndefined()
|
|
599
|
-
expect(deleteResult.success).toBe(true)
|
|
600
|
-
})
|
|
601
|
-
})
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
## 🎯 **Pontos-Chave do Exemplo**
|
|
605
|
-
|
|
606
|
-
### **✅ Type Safety Automática**
|
|
607
|
-
- Types definidos uma vez em `shared/`
|
|
608
|
-
- Eden Treaty infere automaticamente
|
|
609
|
-
- Zero declarações manuais de tipos
|
|
610
|
-
- Autocomplete perfeito no editor
|
|
611
|
-
|
|
612
|
-
### **✅ Arquitetura Limpa**
|
|
613
|
-
- Separação clara: types → controller → routes → client
|
|
614
|
-
- Hook personalizado encapsula lógica
|
|
615
|
-
- Componente focado apenas na UI
|
|
616
|
-
- Error handling consistente
|
|
617
|
-
|
|
618
|
-
### **✅ Eden Treaty Nativo**
|
|
619
|
-
- Sem wrappers que quebram tipos
|
|
620
|
-
- Padrão `{ data, error }` consistente
|
|
621
|
-
- Response schemas para documentação
|
|
622
|
-
- Type inference funcionando 100%
|
|
623
|
-
|
|
624
|
-
---
|
|
625
|
-
|
|
626
|
-
**🎯 Este exemplo mostra como aproveitar ao máximo o FluxStack: type safety automática, arquitetura limpa e desenvolvimento produtivo!**
|