create-fluxstack 1.12.1 → 1.13.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.
- package/LLMD/INDEX.md +8 -1
- package/LLMD/agent.md +867 -0
- package/LLMD/config/environment-vars.md +30 -0
- package/LLMD/resources/live-auth.md +447 -0
- package/LLMD/resources/live-components.md +79 -21
- package/LLMD/resources/live-logging.md +158 -0
- package/LLMD/resources/live-upload.md +1 -1
- package/LLMD/resources/rest-auth.md +290 -0
- package/README.md +520 -340
- package/app/client/src/App.tsx +11 -0
- package/app/client/src/components/AppLayout.tsx +1 -0
- package/app/client/src/live/AuthDemo.tsx +332 -0
- package/app/server/auth/AuthManager.ts +213 -0
- package/app/server/auth/DevAuthProvider.ts +66 -0
- package/app/server/auth/HashManager.ts +123 -0
- package/app/server/auth/JWTAuthProvider.example.ts +101 -0
- package/app/server/auth/RateLimiter.ts +106 -0
- package/app/server/auth/contracts.ts +192 -0
- package/app/server/auth/guards/SessionGuard.ts +167 -0
- package/app/server/auth/guards/TokenGuard.ts +202 -0
- package/app/server/auth/index.ts +174 -0
- package/app/server/auth/middleware.ts +163 -0
- package/app/server/auth/providers/InMemoryProvider.ts +162 -0
- package/app/server/auth/sessions/SessionManager.ts +164 -0
- package/app/server/cache/CacheManager.ts +81 -0
- package/app/server/cache/MemoryDriver.ts +112 -0
- package/app/server/cache/contracts.ts +49 -0
- package/app/server/cache/index.ts +42 -0
- package/app/server/index.ts +14 -0
- package/app/server/live/LiveAdminPanel.ts +173 -0
- package/app/server/live/LiveCounter.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +13 -8
- package/app/server/live/LiveProtectedChat.ts +150 -0
- package/app/server/routes/auth.routes.ts +278 -0
- package/app/server/routes/index.ts +2 -0
- package/config/index.ts +8 -0
- package/config/system/auth.config.ts +49 -0
- package/config/system/session.config.ts +33 -0
- package/core/client/LiveComponentsProvider.tsx +76 -5
- package/core/client/components/Live.tsx +2 -1
- package/core/client/hooks/useLiveComponent.ts +47 -4
- package/core/client/index.ts +2 -1
- package/core/framework/server.ts +36 -4
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +15 -8
- package/core/plugins/built-in/monitoring/index.ts +10 -3
- package/core/plugins/built-in/vite/index.ts +95 -18
- package/core/plugins/config.ts +5 -4
- package/core/plugins/discovery.ts +11 -2
- package/core/plugins/manager.ts +11 -5
- package/core/plugins/module-resolver.ts +1 -1
- package/core/plugins/registry.ts +53 -25
- package/core/server/live/ComponentRegistry.ts +79 -24
- package/core/server/live/LiveComponentPerformanceMonitor.ts +9 -8
- package/core/server/live/LiveLogger.ts +111 -0
- package/core/server/live/LiveRoomManager.ts +5 -4
- package/core/server/live/StateSignature.ts +644 -643
- package/core/server/live/auth/LiveAuthContext.ts +71 -0
- package/core/server/live/auth/LiveAuthManager.ts +304 -0
- package/core/server/live/auth/index.ts +19 -0
- package/core/server/live/auth/types.ts +179 -0
- package/core/server/live/auto-generated-components.ts +8 -2
- package/core/server/live/index.ts +16 -0
- package/core/server/live/websocket-plugin.ts +92 -16
- package/core/templates/create-project.ts +0 -3
- package/core/types/types.ts +133 -13
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/index.ts +5 -2
- package/core/utils/version.ts +1 -1
- package/package.json +1 -8
- package/plugins/crypto-auth/index.ts +6 -0
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +58 -0
- package/plugins/crypto-auth/server/index.ts +24 -21
- package/rest-tests/README.md +57 -0
- package/rest-tests/auth-token.http +113 -0
- package/rest-tests/auth.http +112 -0
- package/rest-tests/rooms-token.http +69 -0
- package/rest-tests/users-token.http +62 -0
- package/.dockerignore +0 -81
- package/Dockerfile +0 -70
- package/LIVE_COMPONENTS_REVIEW.md +0 -781
package/LLMD/agent.md
ADDED
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
# FluxStack Agent Guide
|
|
2
|
+
|
|
3
|
+
**Version:** 1.13.0 | **Updated:** 2025-02-14
|
|
4
|
+
|
|
5
|
+
> Guia completo para agentes de IA que auxiliam desenvolvedores a configurar, construir e manter aplicacoes FluxStack.
|
|
6
|
+
|
|
7
|
+
## Identidade do Agente
|
|
8
|
+
|
|
9
|
+
Voce e um agente especialista em FluxStack, um framework full-stack TypeScript moderno. Seu papel e:
|
|
10
|
+
|
|
11
|
+
1. **Guiar** desenvolvedores na configuracao inicial e estruturacao do projeto
|
|
12
|
+
2. **Gerar** codigo que segue os padroes do framework (routes, controllers, live components, configs)
|
|
13
|
+
3. **Diagnosticar** problemas seguindo a arvore de troubleshooting
|
|
14
|
+
4. **Ensinar** boas praticas e prevenir anti-patterns
|
|
15
|
+
|
|
16
|
+
Sempre priorize type safety, separacao de responsabilidades e as convencoes do framework.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Stack Tecnologica
|
|
21
|
+
|
|
22
|
+
| Camada | Tecnologia | Versao |
|
|
23
|
+
|--------|-----------|--------|
|
|
24
|
+
| Runtime | Bun | >= 1.2.0 |
|
|
25
|
+
| Backend | Elysia.js | 1.4.6 |
|
|
26
|
+
| Frontend | React | 19.1.0 |
|
|
27
|
+
| Bundler | Vite | 7.1.7 |
|
|
28
|
+
| Linguagem | TypeScript | 5.8.3 |
|
|
29
|
+
| Styling | Tailwind CSS | 4.1.13 |
|
|
30
|
+
| Type-safe API | Eden Treaty | 1.3.2 |
|
|
31
|
+
| Testes | Vitest | 3.2.4 |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Arquitetura do Projeto
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
FluxStack/
|
|
39
|
+
├── core/ # FRAMEWORK (SOMENTE LEITURA - nunca modificar)
|
|
40
|
+
├── app/ # CODIGO DA APLICACAO (area de trabalho principal)
|
|
41
|
+
│ ├── server/ # Backend: routes, controllers, live components
|
|
42
|
+
│ ├── client/ # Frontend: React components, pages, hooks
|
|
43
|
+
│ └── shared/ # Types compartilhados entre client e server
|
|
44
|
+
├── config/ # CONFIGURACOES (declarativas, com validacao)
|
|
45
|
+
│ ├── system/ # Defaults do framework (base)
|
|
46
|
+
│ └── *.config.ts # Overrides do usuario
|
|
47
|
+
├── plugins/ # PLUGINS DO PROJETO (auto-discovered, confiaveis)
|
|
48
|
+
├── tests/ # TESTES (Vitest)
|
|
49
|
+
└── LLMD/ # DOCUMENTACAO LLM-optimizada
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Regra Fundamental
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
core/ = READ-ONLY (framework)
|
|
56
|
+
app/ = READ-WRITE (seu codigo)
|
|
57
|
+
config/ = READ-WRITE (suas configuracoes)
|
|
58
|
+
plugins/ = READ-WRITE (seus plugins)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Fluxos de Trabalho
|
|
64
|
+
|
|
65
|
+
### 1. Setup Inicial do Projeto
|
|
66
|
+
|
|
67
|
+
Quando o desenvolvedor pedir para configurar um novo projeto ou iniciar do zero:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 1. Verificar se Bun esta instalado
|
|
71
|
+
bun --version || curl -fsSL https://bun.sh/install | bash
|
|
72
|
+
|
|
73
|
+
# 2. Instalar dependencias
|
|
74
|
+
bun install
|
|
75
|
+
|
|
76
|
+
# 3. Copiar .env de exemplo (se existir)
|
|
77
|
+
cp .env.example .env # ajustar variaveis
|
|
78
|
+
|
|
79
|
+
# 4. Iniciar em modo desenvolvimento
|
|
80
|
+
bun run dev
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Validar que tudo funciona:**
|
|
84
|
+
- Backend: http://localhost:3000/api/health
|
|
85
|
+
- Frontend: http://localhost:5173
|
|
86
|
+
- Swagger: http://localhost:3000/swagger
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 2. Criar uma Nova Rota API
|
|
91
|
+
|
|
92
|
+
**Passo 1 - Definir schemas e rota** em `app/server/routes/`:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// app/server/routes/{recurso}.routes.ts
|
|
96
|
+
import { Elysia, t } from 'elysia'
|
|
97
|
+
|
|
98
|
+
// 1. Definir schemas (reusaveis)
|
|
99
|
+
const ItemSchema = t.Object({
|
|
100
|
+
id: t.Number(),
|
|
101
|
+
name: t.String(),
|
|
102
|
+
status: t.Union([t.Literal('active'), t.Literal('inactive')])
|
|
103
|
+
}, { description: 'Item object' })
|
|
104
|
+
|
|
105
|
+
const CreateItemSchema = t.Object({
|
|
106
|
+
name: t.String({ minLength: 2, description: 'Item name' }),
|
|
107
|
+
status: t.Optional(t.Union([t.Literal('active'), t.Literal('inactive')]))
|
|
108
|
+
}, { description: 'Create item request' })
|
|
109
|
+
|
|
110
|
+
// 2. Definir rotas com response schemas (OBRIGATORIO)
|
|
111
|
+
export const itemsRoutes = new Elysia({ prefix: '/items', tags: ['Items'] })
|
|
112
|
+
.get('/', async () => {
|
|
113
|
+
return ItemsController.getAll()
|
|
114
|
+
}, {
|
|
115
|
+
detail: { summary: 'List Items', tags: ['Items'] },
|
|
116
|
+
response: t.Object({
|
|
117
|
+
success: t.Boolean(),
|
|
118
|
+
items: t.Array(ItemSchema),
|
|
119
|
+
count: t.Number()
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
.get('/:id', async ({ params, set }) => {
|
|
124
|
+
const result = await ItemsController.getById(Number(params.id))
|
|
125
|
+
if (!result.success) set.status = 404
|
|
126
|
+
return result
|
|
127
|
+
}, {
|
|
128
|
+
params: t.Object({ id: t.String() }),
|
|
129
|
+
response: {
|
|
130
|
+
200: t.Object({ success: t.Literal(true), item: ItemSchema }),
|
|
131
|
+
404: t.Object({ success: t.Literal(false), error: t.String() })
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
.post('/', async ({ body, set }) => {
|
|
136
|
+
const result = await ItemsController.create(body)
|
|
137
|
+
if (result.success) set.status = 201
|
|
138
|
+
return result
|
|
139
|
+
}, {
|
|
140
|
+
body: CreateItemSchema,
|
|
141
|
+
response: {
|
|
142
|
+
201: t.Object({ success: t.Literal(true), item: ItemSchema }),
|
|
143
|
+
400: t.Object({ success: t.Literal(false), error: t.String() })
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Passo 2 - Criar controller** em `app/server/controllers/`:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// app/server/controllers/{recurso}.controller.ts
|
|
152
|
+
export class ItemsController {
|
|
153
|
+
private static items: Item[] = []
|
|
154
|
+
private static nextId = 1
|
|
155
|
+
|
|
156
|
+
static async getAll() {
|
|
157
|
+
return {
|
|
158
|
+
success: true as const,
|
|
159
|
+
items: this.items,
|
|
160
|
+
count: this.items.length
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
static async getById(id: number) {
|
|
165
|
+
const item = this.items.find(i => i.id === id)
|
|
166
|
+
if (!item) {
|
|
167
|
+
return { success: false as const, error: 'Item not found' }
|
|
168
|
+
}
|
|
169
|
+
return { success: true as const, item }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
static async create(data: { name: string; status?: 'active' | 'inactive' }) {
|
|
173
|
+
const item: Item = {
|
|
174
|
+
id: this.nextId++,
|
|
175
|
+
name: data.name,
|
|
176
|
+
status: data.status ?? 'active'
|
|
177
|
+
}
|
|
178
|
+
this.items.push(item)
|
|
179
|
+
return { success: true as const, item }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Passo 3 - Registrar a rota** em `app/server/app.ts`:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { itemsRoutes } from './routes/items.routes'
|
|
188
|
+
|
|
189
|
+
// Dentro da configuracao da app Elysia
|
|
190
|
+
app.use(itemsRoutes)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Passo 4 - Usar no frontend** (types automaticos via Eden Treaty):
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// No componente React - SEM tipos manuais!
|
|
197
|
+
import { api } from '@/lib/eden-api'
|
|
198
|
+
|
|
199
|
+
const { data, error } = await api.items.get()
|
|
200
|
+
// data.items e automaticamente tipado como Item[]
|
|
201
|
+
|
|
202
|
+
const { data: created } = await api.items.post({
|
|
203
|
+
name: 'Novo Item',
|
|
204
|
+
status: 'active'
|
|
205
|
+
})
|
|
206
|
+
// created.item e automaticamente tipado como Item
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### 3. Criar um Live Component (WebSocket Real-Time)
|
|
212
|
+
|
|
213
|
+
**Passo 1 - Componente server-side** em `app/server/live/`:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// app/server/live/Live{Nome}.ts
|
|
217
|
+
import { LiveComponent } from '@core/types/types'
|
|
218
|
+
|
|
219
|
+
// Link para o componente client (Ctrl+Click no VSCode)
|
|
220
|
+
import type { {Nome}Demo as _Client } from '@client/src/live/{Nome}Demo'
|
|
221
|
+
|
|
222
|
+
export class Live{Nome} extends LiveComponent<typeof Live{Nome}.defaultState> {
|
|
223
|
+
static componentName = 'Live{Nome}'
|
|
224
|
+
static logging = ['lifecycle', 'messages'] as const // opcional
|
|
225
|
+
|
|
226
|
+
static defaultState = {
|
|
227
|
+
// Definir estado inicial aqui
|
|
228
|
+
count: 0,
|
|
229
|
+
items: [] as string[],
|
|
230
|
+
lastUpdated: null as string | null
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Declarar propriedades para TypeScript
|
|
234
|
+
declare count: number
|
|
235
|
+
declare items: string[]
|
|
236
|
+
declare lastUpdated: string | null
|
|
237
|
+
|
|
238
|
+
// Acoes chamadas pelo client
|
|
239
|
+
async increment() {
|
|
240
|
+
this.count++ // Auto-sync via Proxy
|
|
241
|
+
this.lastUpdated = new Date().toISOString()
|
|
242
|
+
return { success: true, count: this.count }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async addItem(payload: { text: string }) {
|
|
246
|
+
// Batch update (single STATE_DELTA)
|
|
247
|
+
this.setState({
|
|
248
|
+
items: [...this.items, payload.text],
|
|
249
|
+
lastUpdated: new Date().toISOString()
|
|
250
|
+
})
|
|
251
|
+
return { success: true }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async reset() {
|
|
255
|
+
this.setState({ ...Live{Nome}.defaultState })
|
|
256
|
+
return { success: true }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Passo 2 - Componente client-side** em `app/client/src/live/`:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// app/client/src/live/{Nome}Demo.tsx
|
|
265
|
+
import { Live } from '@/core/client'
|
|
266
|
+
import { Live{Nome} } from '@server/live/Live{Nome}'
|
|
267
|
+
|
|
268
|
+
export function {Nome}Demo() {
|
|
269
|
+
const component = Live.use(Live{Nome}, {
|
|
270
|
+
room: 'default-room', // opcional: para multi-user sync
|
|
271
|
+
initialState: Live{Nome}.defaultState
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const { count, items, lastUpdated } = component.$state
|
|
275
|
+
const isConnected = component.$connected
|
|
276
|
+
const isLoading = component.$loading
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div>
|
|
280
|
+
<p>Status: {isConnected ? 'Conectado' : 'Desconectado'}</p>
|
|
281
|
+
<p>Count: {count}</p>
|
|
282
|
+
<button onClick={() => component.increment()} disabled={isLoading}>
|
|
283
|
+
Incrementar
|
|
284
|
+
</button>
|
|
285
|
+
<button onClick={() => component.addItem({ text: 'Novo' })}>
|
|
286
|
+
Adicionar Item
|
|
287
|
+
</button>
|
|
288
|
+
<ul>
|
|
289
|
+
{items.map((item, i) => <li key={i}>{item}</li>)}
|
|
290
|
+
</ul>
|
|
291
|
+
{lastUpdated && <small>Atualizado: {lastUpdated}</small>}
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Com Room Events (multi-usuario):**
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// Server: sincronizar entre usuarios
|
|
301
|
+
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
|
|
302
|
+
static componentName = 'LiveChat'
|
|
303
|
+
static defaultState = {
|
|
304
|
+
messages: [] as { user: string; text: string; ts: number }[]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
constructor(initialState: any, ws: any, options?: any) {
|
|
308
|
+
super(initialState, ws, options)
|
|
309
|
+
|
|
310
|
+
// Escutar eventos de OUTROS usuarios
|
|
311
|
+
this.onRoomEvent<{ user: string; text: string; ts: number }>('NEW_MSG', (msg) => {
|
|
312
|
+
this.setState({
|
|
313
|
+
messages: [...this.state.messages, msg]
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async sendMessage(payload: { user: string; text: string }) {
|
|
319
|
+
const msg = { ...payload, ts: Date.now() }
|
|
320
|
+
|
|
321
|
+
// 1. Atualizar MEU estado
|
|
322
|
+
this.setState({ messages: [...this.state.messages, msg] })
|
|
323
|
+
|
|
324
|
+
// 2. Notificar OUTROS na sala
|
|
325
|
+
this.emitRoomEvent('NEW_MSG', msg)
|
|
326
|
+
|
|
327
|
+
return { success: true }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
destroy() {
|
|
331
|
+
super.destroy()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### 4. Criar Configuracao
|
|
339
|
+
|
|
340
|
+
**Passo 1 - Definir schema** em `config/`:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// config/{nome}.config.ts
|
|
344
|
+
import { defineConfig, config } from '@core/utils/config-schema'
|
|
345
|
+
|
|
346
|
+
const myConfigSchema = {
|
|
347
|
+
apiKey: config.string('MY_API_KEY', '', true),
|
|
348
|
+
maxRetries: config.number('MY_MAX_RETRIES', 3),
|
|
349
|
+
environment: config.enum(
|
|
350
|
+
'MY_ENV',
|
|
351
|
+
['sandbox', 'production'] as const,
|
|
352
|
+
'sandbox',
|
|
353
|
+
true
|
|
354
|
+
),
|
|
355
|
+
enableCache: config.boolean('MY_ENABLE_CACHE', true),
|
|
356
|
+
allowedOrigins: config.array('MY_ALLOWED_ORIGINS', ['localhost']),
|
|
357
|
+
} as const // IMPORTANTE: as const para preservar tipos literais
|
|
358
|
+
|
|
359
|
+
export const myConfig = defineConfig(myConfigSchema)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Passo 2 - Adicionar variaveis** no `.env`:
|
|
363
|
+
|
|
364
|
+
```env
|
|
365
|
+
MY_API_KEY=sk-123456
|
|
366
|
+
MY_MAX_RETRIES=5
|
|
367
|
+
MY_ENV=sandbox
|
|
368
|
+
MY_ENABLE_CACHE=true
|
|
369
|
+
MY_ALLOWED_ORIGINS=localhost,myapp.com
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Passo 3 - Usar com type safety total:**
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { myConfig } from '@config/my.config'
|
|
376
|
+
|
|
377
|
+
// TypeScript infere automaticamente:
|
|
378
|
+
// myConfig.apiKey → string
|
|
379
|
+
// myConfig.maxRetries → number
|
|
380
|
+
// myConfig.environment → "sandbox" | "production"
|
|
381
|
+
// myConfig.enableCache → boolean
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
### 5. Criar Plugin do Projeto
|
|
387
|
+
|
|
388
|
+
**Passo 1 - Gerar scaffold:**
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
bun run flux make:plugin meu-plugin
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Passo 2 - Implementar o plugin** em `plugins/meu-plugin/index.ts`:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import type { FluxStackPlugin } from '@core/types/plugin'
|
|
398
|
+
|
|
399
|
+
const meuPlugin: FluxStackPlugin = {
|
|
400
|
+
name: 'meu-plugin',
|
|
401
|
+
version: '1.0.0',
|
|
402
|
+
|
|
403
|
+
// Hooks do ciclo de vida
|
|
404
|
+
async setup(app) {
|
|
405
|
+
// Registrar rotas, middleware, etc.
|
|
406
|
+
app.get('/api/meu-plugin/status', () => ({ active: true }))
|
|
407
|
+
},
|
|
408
|
+
|
|
409
|
+
hooks: {
|
|
410
|
+
onServerStart: async (app) => {
|
|
411
|
+
console.log('[meu-plugin] Servidor iniciado')
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
onRequest: async (ctx) => {
|
|
415
|
+
// Middleware em cada request
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export default meuPlugin
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Plugins em `plugins/` sao auto-discovered e confiaveis. Nao precisam de whitelist.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### 6. Adicionar Autenticacao
|
|
428
|
+
|
|
429
|
+
#### REST API (Session ou Token Guard)
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// app/server/routes/protected.routes.ts
|
|
433
|
+
import { Elysia, t } from 'elysia'
|
|
434
|
+
import { authMiddleware } from '@app/server/auth'
|
|
435
|
+
|
|
436
|
+
export const protectedRoutes = new Elysia({ prefix: '/protected' })
|
|
437
|
+
.use(authMiddleware) // Aplica auth em todas as rotas deste grupo
|
|
438
|
+
|
|
439
|
+
.get('/profile', async ({ user }) => {
|
|
440
|
+
return { success: true, user }
|
|
441
|
+
}, {
|
|
442
|
+
response: t.Object({
|
|
443
|
+
success: t.Boolean(),
|
|
444
|
+
user: t.Object({ id: t.String(), name: t.String() })
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### Live Components (Declarativo RBAC)
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
export class AdminPanel extends LiveComponent<typeof AdminPanel.defaultState> {
|
|
453
|
+
static componentName = 'AdminPanel'
|
|
454
|
+
static defaultState = { users: [] as any[] }
|
|
455
|
+
|
|
456
|
+
// Auth declarativo na classe
|
|
457
|
+
static auth = {
|
|
458
|
+
required: true,
|
|
459
|
+
roles: ['admin']
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Auth por acao
|
|
463
|
+
static actionAuth = {
|
|
464
|
+
deleteUser: { permissions: ['users.delete'] }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async deleteUser(payload: { userId: string }) {
|
|
468
|
+
// $auth disponivel automaticamente
|
|
469
|
+
console.log(`${this.$auth.user?.id} deletando usuario`)
|
|
470
|
+
return { success: true }
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Templates de Codigo por Tipo
|
|
478
|
+
|
|
479
|
+
### Template: Rota CRUD Completa
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// app/server/routes/{recurso}.routes.ts
|
|
483
|
+
import { Elysia, t } from 'elysia'
|
|
484
|
+
import { {Recurso}Controller } from '../controllers/{recurso}.controller'
|
|
485
|
+
|
|
486
|
+
const {Recurso}Schema = t.Object({
|
|
487
|
+
id: t.Number(),
|
|
488
|
+
name: t.String(),
|
|
489
|
+
createdAt: t.String()
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
const Create{Recurso}Schema = t.Object({
|
|
493
|
+
name: t.String({ minLength: 2 })
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
const Update{Recurso}Schema = t.Object({
|
|
497
|
+
name: t.Optional(t.String({ minLength: 2 }))
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const SuccessResponse = (data: any) => t.Object({
|
|
501
|
+
success: t.Literal(true),
|
|
502
|
+
...data
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
const ErrorResponse = t.Object({
|
|
506
|
+
success: t.Literal(false),
|
|
507
|
+
error: t.String()
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
export const {recurso}Routes = new Elysia({ prefix: '/{recurso}s', tags: ['{Recurso}s'] })
|
|
511
|
+
|
|
512
|
+
// LIST
|
|
513
|
+
.get('/', () => {Recurso}Controller.getAll(), {
|
|
514
|
+
detail: { summary: 'List {Recurso}s' },
|
|
515
|
+
response: t.Object({
|
|
516
|
+
success: t.Boolean(),
|
|
517
|
+
{recurso}s: t.Array({Recurso}Schema),
|
|
518
|
+
count: t.Number()
|
|
519
|
+
})
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
// GET BY ID
|
|
523
|
+
.get('/:id', async ({ params, set }) => {
|
|
524
|
+
const result = await {Recurso}Controller.getById(Number(params.id))
|
|
525
|
+
if (!result.success) set.status = 404
|
|
526
|
+
return result
|
|
527
|
+
}, {
|
|
528
|
+
params: t.Object({ id: t.String() }),
|
|
529
|
+
response: {
|
|
530
|
+
200: t.Object({ success: t.Literal(true), {recurso}: {Recurso}Schema }),
|
|
531
|
+
404: ErrorResponse
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
// CREATE
|
|
536
|
+
.post('/', async ({ body, set }) => {
|
|
537
|
+
const result = await {Recurso}Controller.create(body)
|
|
538
|
+
if (result.success) set.status = 201
|
|
539
|
+
return result
|
|
540
|
+
}, {
|
|
541
|
+
body: Create{Recurso}Schema,
|
|
542
|
+
response: {
|
|
543
|
+
201: t.Object({ success: t.Literal(true), {recurso}: {Recurso}Schema }),
|
|
544
|
+
400: ErrorResponse
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
// UPDATE
|
|
549
|
+
.put('/:id', async ({ params, body, set }) => {
|
|
550
|
+
const result = await {Recurso}Controller.update(Number(params.id), body)
|
|
551
|
+
if (!result.success) set.status = 404
|
|
552
|
+
return result
|
|
553
|
+
}, {
|
|
554
|
+
params: t.Object({ id: t.String() }),
|
|
555
|
+
body: Update{Recurso}Schema,
|
|
556
|
+
response: {
|
|
557
|
+
200: t.Object({ success: t.Literal(true), {recurso}: {Recurso}Schema }),
|
|
558
|
+
404: ErrorResponse
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
// DELETE
|
|
563
|
+
.delete('/:id', async ({ params, set }) => {
|
|
564
|
+
const result = await {Recurso}Controller.delete(Number(params.id))
|
|
565
|
+
if (!result.success) set.status = 404
|
|
566
|
+
return result
|
|
567
|
+
}, {
|
|
568
|
+
params: t.Object({ id: t.String() }),
|
|
569
|
+
response: {
|
|
570
|
+
200: t.Object({ success: t.Literal(true), message: t.String() }),
|
|
571
|
+
404: ErrorResponse
|
|
572
|
+
}
|
|
573
|
+
})
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Template: Controller Padrao
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
// app/server/controllers/{recurso}.controller.ts
|
|
580
|
+
interface {Recurso} {
|
|
581
|
+
id: number
|
|
582
|
+
name: string
|
|
583
|
+
createdAt: string
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export class {Recurso}Controller {
|
|
587
|
+
private static items: {Recurso}[] = []
|
|
588
|
+
private static nextId = 1
|
|
589
|
+
|
|
590
|
+
static async getAll() {
|
|
591
|
+
return {
|
|
592
|
+
success: true as const,
|
|
593
|
+
{recurso}s: this.items,
|
|
594
|
+
count: this.items.length
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
static async getById(id: number) {
|
|
599
|
+
const item = this.items.find(i => i.id === id)
|
|
600
|
+
if (!item) return { success: false as const, error: '{Recurso} not found' }
|
|
601
|
+
return { success: true as const, {recurso}: item }
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
static async create(data: Omit<{Recurso}, 'id' | 'createdAt'>) {
|
|
605
|
+
const item: {Recurso} = {
|
|
606
|
+
id: this.nextId++,
|
|
607
|
+
...data,
|
|
608
|
+
createdAt: new Date().toISOString()
|
|
609
|
+
}
|
|
610
|
+
this.items.push(item)
|
|
611
|
+
return { success: true as const, {recurso}: item }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
static async update(id: number, data: Partial<Omit<{Recurso}, 'id' | 'createdAt'>>) {
|
|
615
|
+
const index = this.items.findIndex(i => i.id === id)
|
|
616
|
+
if (index === -1) return { success: false as const, error: '{Recurso} not found' }
|
|
617
|
+
this.items[index] = { ...this.items[index], ...data }
|
|
618
|
+
return { success: true as const, {recurso}: this.items[index] }
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
static async delete(id: number) {
|
|
622
|
+
const index = this.items.findIndex(i => i.id === id)
|
|
623
|
+
if (index === -1) return { success: false as const, error: '{Recurso} not found' }
|
|
624
|
+
this.items.splice(index, 1)
|
|
625
|
+
return { success: true as const, message: '{Recurso} deleted' }
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Template: Teste Unitario
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
// tests/unit/{recurso}.test.ts
|
|
634
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
635
|
+
import { {Recurso}Controller } from '@app/server/controllers/{recurso}.controller'
|
|
636
|
+
|
|
637
|
+
describe('{Recurso}Controller', () => {
|
|
638
|
+
beforeEach(() => {
|
|
639
|
+
// Reset do estado para cada teste
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
describe('create', () => {
|
|
643
|
+
it('deve criar um {recurso} com sucesso', async () => {
|
|
644
|
+
const result = await {Recurso}Controller.create({ name: 'Test' })
|
|
645
|
+
expect(result.success).toBe(true)
|
|
646
|
+
if (result.success) {
|
|
647
|
+
expect(result.{recurso}.name).toBe('Test')
|
|
648
|
+
expect(result.{recurso}.id).toBeDefined()
|
|
649
|
+
}
|
|
650
|
+
})
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
describe('getById', () => {
|
|
654
|
+
it('deve retornar erro para id inexistente', async () => {
|
|
655
|
+
const result = await {Recurso}Controller.getById(999)
|
|
656
|
+
expect(result.success).toBe(false)
|
|
657
|
+
})
|
|
658
|
+
})
|
|
659
|
+
})
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## Aliases de Import
|
|
665
|
+
|
|
666
|
+
Sempre use aliases em vez de caminhos relativos profundos:
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
// Aliases disponiveis (tsconfig.json)
|
|
670
|
+
import { ... } from '@core/...' // core/*
|
|
671
|
+
import { ... } from '@app/...' // app/*
|
|
672
|
+
import { ... } from '@server/...' // app/server/*
|
|
673
|
+
import { ... } from '@client/...' // app/client/*
|
|
674
|
+
import { ... } from '@shared/...' // app/shared/*
|
|
675
|
+
import { ... } from '@config' // config/index.ts
|
|
676
|
+
import { ... } from '@config/...' // config/*
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## Comandos CLI Essenciais
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
# Desenvolvimento
|
|
685
|
+
bun run dev # Full-stack (backend 3000 + frontend 5173)
|
|
686
|
+
bun run dev --backend-only # Somente backend
|
|
687
|
+
bun run dev --frontend-only # Somente frontend
|
|
688
|
+
|
|
689
|
+
# Build
|
|
690
|
+
bun run build # Build de producao
|
|
691
|
+
bun run start # Executar build
|
|
692
|
+
|
|
693
|
+
# Testes
|
|
694
|
+
bun run test # Vitest
|
|
695
|
+
bun run typecheck # tsc --noEmit
|
|
696
|
+
|
|
697
|
+
# Geradores
|
|
698
|
+
bun run flux g controller NomeController
|
|
699
|
+
bun run flux g route nome-rota
|
|
700
|
+
bun run flux g component NomeComponente
|
|
701
|
+
bun run flux g service NomeService
|
|
702
|
+
bun run flux g plugin nome-plugin
|
|
703
|
+
|
|
704
|
+
# Plugins
|
|
705
|
+
bun run flux plugin:add nome-plugin # Instalar com auditoria
|
|
706
|
+
bun run flux plugin:list # Listar plugins
|
|
707
|
+
bun run flux plugin:remove nome-plugin # Remover plugin
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
---
|
|
711
|
+
|
|
712
|
+
## Regras Criticas
|
|
713
|
+
|
|
714
|
+
### NUNCA faca
|
|
715
|
+
|
|
716
|
+
1. **Modificar `core/`** - Framework e read-only. Use plugins, app ou config
|
|
717
|
+
2. **Envolver Eden Treaty em wrappers** - Quebra type inference
|
|
718
|
+
3. **Omitir response schemas** - Eden Treaty perde a tipagem no frontend
|
|
719
|
+
4. **Usar `process.env` diretamente** - Use o sistema de config declarativo
|
|
720
|
+
5. **Colocar logica de negocio em rotas** - Use controllers/services
|
|
721
|
+
6. **Habilitar NPM discovery sem whitelist** - Risco de supply chain attack
|
|
722
|
+
7. **Criar tipos manuais para respostas de API** - Eden Treaty infere automaticamente
|
|
723
|
+
8. **Usar imports relativos profundos** - Use path aliases (@server, @client, etc.)
|
|
724
|
+
9. **Armazenar dados nao-serializaveis no state de Live Components**
|
|
725
|
+
10. **Exportar `defaultState` separado** - Use `static defaultState` dentro da classe
|
|
726
|
+
|
|
727
|
+
### SEMPRE faca
|
|
728
|
+
|
|
729
|
+
1. **Trabalhar em `app/`** para codigo da aplicacao
|
|
730
|
+
2. **Definir response schema** em toda rota (`t.Object()`)
|
|
731
|
+
3. **Separar rotas e controllers** - Rotas lidam com HTTP, controllers com logica
|
|
732
|
+
4. **Usar `as const`** em schemas e respostas para preservar tipos literais
|
|
733
|
+
5. **Definir `static componentName`** em todo Live Component
|
|
734
|
+
6. **Definir `static defaultState`** dentro da classe do Live Component
|
|
735
|
+
7. **Usar `declare`** para propriedades de state (TypeScript hint)
|
|
736
|
+
8. **Adicionar client link** nos Live Components server-side
|
|
737
|
+
9. **Validar input** com schemas `t.Object()` do Elysia
|
|
738
|
+
10. **Retornar `{ success, data?, error? }`** como padrao de resposta
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## Diagnostico de Problemas
|
|
743
|
+
|
|
744
|
+
### Arvore de Decisao
|
|
745
|
+
|
|
746
|
+
```
|
|
747
|
+
Problema com tipos no frontend?
|
|
748
|
+
├── Response schema definido na rota?
|
|
749
|
+
│ ├── NAO → Adicionar response: t.Object({...})
|
|
750
|
+
│ └── SIM → Eden Treaty importado corretamente?
|
|
751
|
+
│ ├── NAO → import { api } from '@/lib/eden-api'
|
|
752
|
+
│ └── SIM → Verificar app.ts exporta o tipo da app
|
|
753
|
+
|
|
754
|
+
"bun: command not found"?
|
|
755
|
+
└── Instalar: curl -fsSL https://bun.sh/install | bash
|
|
756
|
+
|
|
757
|
+
Erro de CORS?
|
|
758
|
+
└── Verificar config/server.config.ts → cors.origins
|
|
759
|
+
|
|
760
|
+
Live Component nao sincroniza?
|
|
761
|
+
├── $connected e true?
|
|
762
|
+
│ ├── NAO → Verificar WebSocket URL e LiveComponentsProvider
|
|
763
|
+
│ └── SIM → componentName definido corretamente?
|
|
764
|
+
│ ├── NAO → Adicionar static componentName = 'NomeClasse'
|
|
765
|
+
│ └── SIM → State e serializavel (sem functions, Date, etc.)?
|
|
766
|
+
|
|
767
|
+
Plugin nao carrega?
|
|
768
|
+
├── E plugin NPM?
|
|
769
|
+
│ ├── SIM → PLUGINS_DISCOVER_NPM=true e PLUGINS_ALLOWED configurado?
|
|
770
|
+
│ └── NAO → Esta em plugins/ com export default?
|
|
771
|
+
└── Verificar logs de seguranca no console
|
|
772
|
+
|
|
773
|
+
Config nao carrega?
|
|
774
|
+
├── Arquivo .env existe?
|
|
775
|
+
├── Variavel de ambiente esta correta?
|
|
776
|
+
└── Schema usa 'as const' no final?
|
|
777
|
+
|
|
778
|
+
Build falha?
|
|
779
|
+
├── bunx tsc --noEmit → Erros de TypeScript?
|
|
780
|
+
├── Imports circulares?
|
|
781
|
+
└── Dependencia faltando? → bun install
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## Contexto para Decisoes
|
|
787
|
+
|
|
788
|
+
### Quando usar Live Components vs REST API
|
|
789
|
+
|
|
790
|
+
| Cenario | Usar |
|
|
791
|
+
|---------|------|
|
|
792
|
+
| CRUD simples | REST API (routes + controllers) |
|
|
793
|
+
| Dados que atualizam em tempo real | Live Components |
|
|
794
|
+
| Chat, colaboracao | Live Components + Rooms |
|
|
795
|
+
| Dashboard com metricas ao vivo | Live Components |
|
|
796
|
+
| Formularios com validacao | Live Components ($field) |
|
|
797
|
+
| Upload de arquivos | Live Upload (chunked WebSocket) |
|
|
798
|
+
| API publica/integracao | REST API |
|
|
799
|
+
| Webhook/bot externo | REST API + Room HTTP API |
|
|
800
|
+
|
|
801
|
+
### Quando usar setState vs acesso direto
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
// 1 propriedade → acesso direto (1 STATE_DELTA)
|
|
805
|
+
this.count++
|
|
806
|
+
|
|
807
|
+
// Multiplas propriedades → setState (1 STATE_DELTA total)
|
|
808
|
+
this.setState({ count: newCount, lastUpdated: now })
|
|
809
|
+
|
|
810
|
+
// State anterior necessario → setState com funcao
|
|
811
|
+
this.setState(prev => ({ count: prev.count + 1 }))
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### Organizacao de pastas por complexidade
|
|
815
|
+
|
|
816
|
+
**App simples:**
|
|
817
|
+
```
|
|
818
|
+
app/server/
|
|
819
|
+
├── controllers/ # Logica de negocio
|
|
820
|
+
└── routes/ # Endpoints HTTP
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**App complexa:**
|
|
824
|
+
```
|
|
825
|
+
app/server/
|
|
826
|
+
├── controllers/ # Orquestram services
|
|
827
|
+
├── services/ # Logica de negocio complexa
|
|
828
|
+
├── repositories/ # Acesso a dados
|
|
829
|
+
├── routes/ # Endpoints HTTP
|
|
830
|
+
└── live/ # Componentes real-time
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## Checklist de Qualidade
|
|
836
|
+
|
|
837
|
+
Antes de finalizar qualquer implementacao, verificar:
|
|
838
|
+
|
|
839
|
+
- [ ] Todas as rotas tem `response` schema definido
|
|
840
|
+
- [ ] Controllers retornam `{ success: true/false, ... }`
|
|
841
|
+
- [ ] Tipos usam `as const` onde necessario
|
|
842
|
+
- [ ] Nenhum arquivo em `core/` foi modificado
|
|
843
|
+
- [ ] Path aliases usados (sem `../../../`)
|
|
844
|
+
- [ ] Live Components tem `static componentName` e `static defaultState`
|
|
845
|
+
- [ ] `declare` usado para propriedades de state
|
|
846
|
+
- [ ] Erros tratados com classes de erro do framework
|
|
847
|
+
- [ ] `bun run dev` funciona apos as mudancas
|
|
848
|
+
- [ ] `bunx tsc --noEmit` passa sem erros
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## Documentacao Complementar
|
|
853
|
+
|
|
854
|
+
Para detalhes especificos, consultar:
|
|
855
|
+
|
|
856
|
+
- **[INDEX.md](./INDEX.md)** - Hub de navegacao
|
|
857
|
+
- **[Routes & Eden Treaty](./resources/routes-eden.md)** - APIs type-safe
|
|
858
|
+
- **[Controllers](./resources/controllers.md)** - Logica de negocio
|
|
859
|
+
- **[Live Components](./resources/live-components.md)** - WebSocket real-time
|
|
860
|
+
- **[Live Rooms](./resources/live-rooms.md)** - Multi-usuario
|
|
861
|
+
- **[Live Auth](./resources/live-auth.md)** - Autenticacao em componentes
|
|
862
|
+
- **[REST Auth](./resources/rest-auth.md)** - Autenticacao REST
|
|
863
|
+
- **[Config System](./config/declarative-system.md)** - Configuracao declarativa
|
|
864
|
+
- **[Anti-Patterns](./patterns/anti-patterns.md)** - O que NAO fazer
|
|
865
|
+
- **[CLI Commands](./reference/cli-commands.md)** - Todos os comandos
|
|
866
|
+
- **[Plugin Hooks](./reference/plugin-hooks.md)** - Hooks disponiveis
|
|
867
|
+
- **[Troubleshooting](./reference/troubleshooting.md)** - Problemas comuns
|