Version: 1.13.0 | Updated: 2025-02-14
Guia completo para agentes de IA que auxiliam desenvolvedores a configurar, construir e manter aplicacoes FluxStack.
Voce e um agente especialista em FluxStack, um framework full-stack TypeScript moderno. Seu papel e:
- Guiar desenvolvedores na configuracao inicial e estruturacao do projeto
- Gerar codigo que segue os padroes do framework (routes, controllers, live components, configs)
- Diagnosticar problemas seguindo a arvore de troubleshooting
- Ensinar boas praticas e prevenir anti-patterns
Sempre priorize type safety, separacao de responsabilidades e as convencoes do framework.
| Camada | Tecnologia | Versao |
|---|---|---|
| Runtime | Bun | >= 1.2.0 |
| Backend | Elysia.js | 1.4.6 |
| Frontend | React | 19.1.0 |
| Bundler | Vite | 7.1.7 |
| Linguagem | TypeScript | 5.8.3 |
| Styling | Tailwind CSS | 4.1.13 |
| Type-safe API | Eden Treaty | 1.3.2 |
| Testes | Vitest | 3.2.4 |
FluxStack/
├── core/ # FRAMEWORK (SOMENTE LEITURA - nunca modificar)
├── app/ # CODIGO DA APLICACAO (area de trabalho principal)
│ ├── server/ # Backend: routes, controllers, live components
│ ├── client/ # Frontend: React components, pages, hooks
│ └── shared/ # Types compartilhados entre client e server
├── config/ # CONFIGURACOES (declarativas, com validacao)
│ ├── system/ # Defaults do framework (base)
│ └── *.config.ts # Overrides do usuario
├── plugins/ # PLUGINS DO PROJETO (auto-discovered, confiaveis)
├── tests/ # TESTES (Vitest)
└── LLMD/ # DOCUMENTACAO LLM-optimizada
core/ = READ-ONLY (framework)
app/ = READ-WRITE (seu codigo)
config/ = READ-WRITE (suas configuracoes)
plugins/ = READ-WRITE (seus plugins)
Quando o desenvolvedor pedir para configurar um novo projeto ou iniciar do zero:
# 1. Verificar se Bun esta instalado
bun --version || curl -fsSL https://bun.sh/install | bash
# 2. Instalar dependencias
bun install
# 3. Copiar .env de exemplo (se existir)
cp .env.example .env # ajustar variaveis
# 4. Iniciar em modo desenvolvimento
bun run devValidar que tudo funciona:
- Backend: http://localhost:3000/api/health
- Frontend: http://localhost:5173
- Swagger: http://localhost:3000/swagger
Passo 1 - Definir schemas e rota em app/server/routes/:
// app/server/routes/{recurso}.routes.ts
import { Elysia, t } from 'elysia'
// 1. Definir schemas (reusaveis)
const ItemSchema = t.Object({
id: t.Number(),
name: t.String(),
status: t.Union([t.Literal('active'), t.Literal('inactive')])
}, { description: 'Item object' })
const CreateItemSchema = t.Object({
name: t.String({ minLength: 2, description: 'Item name' }),
status: t.Optional(t.Union([t.Literal('active'), t.Literal('inactive')]))
}, { description: 'Create item request' })
// 2. Definir rotas com response schemas (OBRIGATORIO)
export const itemsRoutes = new Elysia({ prefix: '/items', tags: ['Items'] })
.get('/', async () => {
return ItemsController.getAll()
}, {
detail: { summary: 'List Items', tags: ['Items'] },
response: t.Object({
success: t.Boolean(),
items: t.Array(ItemSchema),
count: t.Number()
})
})
.get('/:id', async ({ params, set }) => {
const result = await ItemsController.getById(Number(params.id))
if (!result.success) set.status = 404
return result
}, {
params: t.Object({ id: t.String() }),
response: {
200: t.Object({ success: t.Literal(true), item: ItemSchema }),
404: t.Object({ success: t.Literal(false), error: t.String() })
}
})
.post('/', async ({ body, set }) => {
const result = await ItemsController.create(body)
if (result.success) set.status = 201
return result
}, {
body: CreateItemSchema,
response: {
201: t.Object({ success: t.Literal(true), item: ItemSchema }),
400: t.Object({ success: t.Literal(false), error: t.String() })
}
})Passo 2 - Criar controller em app/server/controllers/:
// app/server/controllers/{recurso}.controller.ts
export class ItemsController {
private static items: Item[] = []
private static nextId = 1
static async getAll() {
return {
success: true as const,
items: this.items,
count: this.items.length
}
}
static async getById(id: number) {
const item = this.items.find(i => i.id === id)
if (!item) {
return { success: false as const, error: 'Item not found' }
}
return { success: true as const, item }
}
static async create(data: { name: string; status?: 'active' | 'inactive' }) {
const item: Item = {
id: this.nextId++,
name: data.name,
status: data.status ?? 'active'
}
this.items.push(item)
return { success: true as const, item }
}
}Passo 3 - Registrar a rota em app/server/app.ts:
import { itemsRoutes } from './routes/items.routes'
// Dentro da configuracao da app Elysia
app.use(itemsRoutes)Passo 4 - Usar no frontend (types automaticos via Eden Treaty):
// No componente React - SEM tipos manuais!
import { api } from '@/lib/eden-api'
const { data, error } = await api.items.get()
// data.items e automaticamente tipado como Item[]
const { data: created } = await api.items.post({
name: 'Novo Item',
status: 'active'
})
// created.item e automaticamente tipado como ItemPasso 1 - Componente server-side em app/server/live/:
// app/server/live/Live{Nome}.ts
import { LiveComponent } from '@core/types/types'
// Link para o componente client (Ctrl+Click no VSCode)
import type { {Nome}Demo as _Client } from '@client/src/live/{Nome}Demo'
export class Live{Nome} extends LiveComponent<typeof Live{Nome}.defaultState> {
static componentName = 'Live{Nome}'
static logging = ['lifecycle', 'messages'] as const // opcional
static defaultState = {
// Definir estado inicial aqui
count: 0,
items: [] as string[],
lastUpdated: null as string | null
}
// Declarar propriedades para TypeScript
declare count: number
declare items: string[]
declare lastUpdated: string | null
// Acoes chamadas pelo client
async increment() {
this.count++ // Auto-sync via Proxy
this.lastUpdated = new Date().toISOString()
return { success: true, count: this.count }
}
async addItem(payload: { text: string }) {
// Batch update (single STATE_DELTA)
this.setState({
items: [...this.items, payload.text],
lastUpdated: new Date().toISOString()
})
return { success: true }
}
async reset() {
this.setState({ ...Live{Nome}.defaultState })
return { success: true }
}
}Passo 2 - Componente client-side em app/client/src/live/:
// app/client/src/live/{Nome}Demo.tsx
import { Live } from '@/core/client'
import { Live{Nome} } from '@server/live/Live{Nome}'
export function {Nome}Demo() {
const component = Live.use(Live{Nome}, {
room: 'default-room', // opcional: para multi-user sync
initialState: Live{Nome}.defaultState
})
const { count, items, lastUpdated } = component.$state
const isConnected = component.$connected
const isLoading = component.$loading
return (
<div>
<p>Status: {isConnected ? 'Conectado' : 'Desconectado'}</p>
<p>Count: {count}</p>
<button onClick={() => component.increment()} disabled={isLoading}>
Incrementar
</button>
<button onClick={() => component.addItem({ text: 'Novo' })}>
Adicionar Item
</button>
<ul>
{items.map((item, i) => <li key={i}>{item}</li>)}
</ul>
{lastUpdated && <small>Atualizado: {lastUpdated}</small>}
</div>
)
}Com Room Events (multi-usuario):
// Server: sincronizar entre usuarios
export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
static componentName = 'LiveChat'
static defaultState = {
messages: [] as { user: string; text: string; ts: number }[]
}
constructor(initialState: any, ws: any, options?: any) {
super(initialState, ws, options)
// Escutar eventos de OUTROS usuarios
this.onRoomEvent<{ user: string; text: string; ts: number }>('NEW_MSG', (msg) => {
this.setState({
messages: [...this.state.messages, msg]
})
})
}
async sendMessage(payload: { user: string; text: string }) {
const msg = { ...payload, ts: Date.now() }
// 1. Atualizar MEU estado
this.setState({ messages: [...this.state.messages, msg] })
// 2. Notificar OUTROS na sala
this.emitRoomEvent('NEW_MSG', msg)
return { success: true }
}
destroy() {
super.destroy()
}
}Passo 1 - Definir schema em config/:
// config/{nome}.config.ts
import { defineConfig, config } from '@core/utils/config-schema'
const myConfigSchema = {
apiKey: config.string('MY_API_KEY', '', true),
maxRetries: config.number('MY_MAX_RETRIES', 3),
environment: config.enum(
'MY_ENV',
['sandbox', 'production'] as const,
'sandbox',
true
),
enableCache: config.boolean('MY_ENABLE_CACHE', true),
allowedOrigins: config.array('MY_ALLOWED_ORIGINS', ['localhost']),
} as const // IMPORTANTE: as const para preservar tipos literais
export const myConfig = defineConfig(myConfigSchema)Passo 2 - Adicionar variaveis no .env:
MY_API_KEY=sk-123456
MY_MAX_RETRIES=5
MY_ENV=sandbox
MY_ENABLE_CACHE=true
MY_ALLOWED_ORIGINS=localhost,myapp.comPasso 3 - Usar com type safety total:
import { myConfig } from '@config/my.config'
// TypeScript infere automaticamente:
// myConfig.apiKey → string
// myConfig.maxRetries → number
// myConfig.environment → "sandbox" | "production"
// myConfig.enableCache → booleanPasso 1 - Gerar scaffold:
bun run flux make:plugin meu-pluginPasso 2 - Implementar o plugin em plugins/meu-plugin/index.ts:
import type { FluxStackPlugin } from '@core/types/plugin'
const meuPlugin: FluxStackPlugin = {
name: 'meu-plugin',
version: '1.0.0',
// Hooks do ciclo de vida
async setup(app) {
// Registrar rotas, middleware, etc.
app.get('/api/meu-plugin/status', () => ({ active: true }))
},
hooks: {
onServerStart: async (app) => {
console.log('[meu-plugin] Servidor iniciado')
},
onRequest: async (ctx) => {
// Middleware em cada request
}
}
}
export default meuPluginPlugins em plugins/ sao auto-discovered e confiaveis. Nao precisam de whitelist.
// app/server/routes/protected.routes.ts
import { Elysia, t } from 'elysia'
import { authMiddleware } from '@app/server/auth'
export const protectedRoutes = new Elysia({ prefix: '/protected' })
.use(authMiddleware) // Aplica auth em todas as rotas deste grupo
.get('/profile', async ({ user }) => {
return { success: true, user }
}, {
response: t.Object({
success: t.Boolean(),
user: t.Object({ id: t.String(), name: t.String() })
})
})export class AdminPanel extends LiveComponent<typeof AdminPanel.defaultState> {
static componentName = 'AdminPanel'
static defaultState = { users: [] as any[] }
// Auth declarativo na classe
static auth = {
required: true,
roles: ['admin']
}
// Auth por acao
static actionAuth = {
deleteUser: { permissions: ['users.delete'] }
}
async deleteUser(payload: { userId: string }) {
// $auth disponivel automaticamente
console.log(`${this.$auth.user?.id} deletando usuario`)
return { success: true }
}
}// app/server/routes/{recurso}.routes.ts
import { Elysia, t } from 'elysia'
import { {Recurso}Controller } from '../controllers/{recurso}.controller'
const {Recurso}Schema = t.Object({
id: t.Number(),
name: t.String(),
createdAt: t.String()
})
const Create{Recurso}Schema = t.Object({
name: t.String({ minLength: 2 })
})
const Update{Recurso}Schema = t.Object({
name: t.Optional(t.String({ minLength: 2 }))
})
const SuccessResponse = (data: any) => t.Object({
success: t.Literal(true),
...data
})
const ErrorResponse = t.Object({
success: t.Literal(false),
error: t.String()
})
export const {recurso}Routes = new Elysia({ prefix: '/{recurso}s', tags: ['{Recurso}s'] })
// LIST
.get('/', () => {Recurso}Controller.getAll(), {
detail: { summary: 'List {Recurso}s' },
response: t.Object({
success: t.Boolean(),
{recurso}s: t.Array({Recurso}Schema),
count: t.Number()
})
})
// GET BY ID
.get('/:id', async ({ params, set }) => {
const result = await {Recurso}Controller.getById(Number(params.id))
if (!result.success) set.status = 404
return result
}, {
params: t.Object({ id: t.String() }),
response: {
200: t.Object({ success: t.Literal(true), {recurso}: {Recurso}Schema }),
404: ErrorResponse
}
})
// CREATE
.post('/', async ({ body, set }) => {
const result = await {Recurso}Controller.create(body)
if (result.success) set.status = 201
return result
}, {
body: Create{Recurso}Schema,
response: {
201: t.Object({ success: t.Literal(true), {recurso}: {Recurso}Schema }),
400: ErrorResponse
}
})
// UPDATE
.put('/:id', async ({ params, body, set }) => {
const result = await {Recurso}Controller.update(Number(params.id), body)
if (!result.success) set.status = 404
return result
}, {
params: t.Object({ id: t.String() }),
body: Update{Recurso}Schema,
response: {
200: t.Object({ success: t.Literal(true), {recurso}: {Recurso}Schema }),
404: ErrorResponse
}
})
// DELETE
.delete('/:id', async ({ params, set }) => {
const result = await {Recurso}Controller.delete(Number(params.id))
if (!result.success) set.status = 404
return result
}, {
params: t.Object({ id: t.String() }),
response: {
200: t.Object({ success: t.Literal(true), message: t.String() }),
404: ErrorResponse
}
})// app/server/controllers/{recurso}.controller.ts
interface {Recurso} {
id: number
name: string
createdAt: string
}
export class {Recurso}Controller {
private static items: {Recurso}[] = []
private static nextId = 1
static async getAll() {
return {
success: true as const,
{recurso}s: this.items,
count: this.items.length
}
}
static async getById(id: number) {
const item = this.items.find(i => i.id === id)
if (!item) return { success: false as const, error: '{Recurso} not found' }
return { success: true as const, {recurso}: item }
}
static async create(data: Omit<{Recurso}, 'id' | 'createdAt'>) {
const item: {Recurso} = {
id: this.nextId++,
...data,
createdAt: new Date().toISOString()
}
this.items.push(item)
return { success: true as const, {recurso}: item }
}
static async update(id: number, data: Partial<Omit<{Recurso}, 'id' | 'createdAt'>>) {
const index = this.items.findIndex(i => i.id === id)
if (index === -1) return { success: false as const, error: '{Recurso} not found' }
this.items[index] = { ...this.items[index], ...data }
return { success: true as const, {recurso}: this.items[index] }
}
static async delete(id: number) {
const index = this.items.findIndex(i => i.id === id)
if (index === -1) return { success: false as const, error: '{Recurso} not found' }
this.items.splice(index, 1)
return { success: true as const, message: '{Recurso} deleted' }
}
}// tests/unit/{recurso}.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { {Recurso}Controller } from '@app/server/controllers/{recurso}.controller'
describe('{Recurso}Controller', () => {
beforeEach(() => {
// Reset do estado para cada teste
})
describe('create', () => {
it('deve criar um {recurso} com sucesso', async () => {
const result = await {Recurso}Controller.create({ name: 'Test' })
expect(result.success).toBe(true)
if (result.success) {
expect(result.{recurso}.name).toBe('Test')
expect(result.{recurso}.id).toBeDefined()
}
})
})
describe('getById', () => {
it('deve retornar erro para id inexistente', async () => {
const result = await {Recurso}Controller.getById(999)
expect(result.success).toBe(false)
})
})
})Sempre use aliases em vez de caminhos relativos profundos:
// Aliases disponiveis (tsconfig.json)
import { ... } from '@core/...' // core/*
import { ... } from '@app/...' // app/*
import { ... } from '@server/...' // app/server/*
import { ... } from '@client/...' // app/client/*
import { ... } from '@shared/...' // app/shared/*
import { ... } from '@config' // config/index.ts
import { ... } from '@config/...' // config/*# Desenvolvimento
bun run dev # Full-stack (backend 3000 + frontend 5173)
bun run dev --backend-only # Somente backend
bun run dev --frontend-only # Somente frontend
# Build
bun run build # Build de producao
bun run start # Executar build
# Testes
bun run test # Vitest
bun run typecheck # tsc --noEmit
# Geradores
bun run flux g controller NomeController
bun run flux g route nome-rota
bun run flux g component NomeComponente
bun run flux g service NomeService
bun run flux g plugin nome-plugin
# Plugins
bun run flux plugin:add nome-plugin # Instalar com auditoria
bun run flux plugin:list # Listar plugins
bun run flux plugin:remove nome-plugin # Remover plugin- Modificar
core/- Framework e read-only. Use plugins, app ou config - Envolver Eden Treaty em wrappers - Quebra type inference
- Omitir response schemas - Eden Treaty perde a tipagem no frontend
- Usar
process.envdiretamente - Use o sistema de config declarativo - Colocar logica de negocio em rotas - Use controllers/services
- Habilitar NPM discovery sem whitelist - Risco de supply chain attack
- Criar tipos manuais para respostas de API - Eden Treaty infere automaticamente
- Usar imports relativos profundos - Use path aliases (@server, @client, etc.)
- Armazenar dados nao-serializaveis no state de Live Components
- Exportar
defaultStateseparado - Usestatic defaultStatedentro da classe
- Trabalhar em
app/para codigo da aplicacao - Definir response schema em toda rota (
t.Object()) - Separar rotas e controllers - Rotas lidam com HTTP, controllers com logica
- Usar
as constem schemas e respostas para preservar tipos literais - Definir
static componentNameem todo Live Component - Definir
static defaultStatedentro da classe do Live Component - Usar
declarepara propriedades de state (TypeScript hint) - Adicionar client link nos Live Components server-side
- Validar input com schemas
t.Object()do Elysia - Retornar
{ success, data?, error? }como padrao de resposta
Problema com tipos no frontend?
├── Response schema definido na rota?
│ ├── NAO → Adicionar response: t.Object({...})
│ └── SIM → Eden Treaty importado corretamente?
│ ├── NAO → import { api } from '@/lib/eden-api'
│ └── SIM → Verificar app.ts exporta o tipo da app
"bun: command not found"?
└── Instalar: curl -fsSL https://bun.sh/install | bash
Erro de CORS?
└── Verificar config/server.config.ts → cors.origins
Live Component nao sincroniza?
├── $connected e true?
│ ├── NAO → Verificar WebSocket URL e LiveComponentsProvider
│ └── SIM → componentName definido corretamente?
│ ├── NAO → Adicionar static componentName = 'NomeClasse'
│ └── SIM → State e serializavel (sem functions, Date, etc.)?
Plugin nao carrega?
├── E plugin NPM?
│ ├── SIM → PLUGINS_DISCOVER_NPM=true e PLUGINS_ALLOWED configurado?
│ └── NAO → Esta em plugins/ com export default?
└── Verificar logs de seguranca no console
Config nao carrega?
├── Arquivo .env existe?
├── Variavel de ambiente esta correta?
└── Schema usa 'as const' no final?
Build falha?
├── bunx tsc --noEmit → Erros de TypeScript?
├── Imports circulares?
└── Dependencia faltando? → bun install
| Cenario | Usar |
|---|---|
| CRUD simples | REST API (routes + controllers) |
| Dados que atualizam em tempo real | Live Components |
| Chat, colaboracao | Live Components + Rooms |
| Dashboard com metricas ao vivo | Live Components |
| Formularios com validacao | Live Components ($field) |
| Upload de arquivos | Live Upload (chunked WebSocket) |
| API publica/integracao | REST API |
| Webhook/bot externo | REST API + Room HTTP API |
// 1 propriedade → acesso direto (1 STATE_DELTA)
this.count++
// Multiplas propriedades → setState (1 STATE_DELTA total)
this.setState({ count: newCount, lastUpdated: now })
// State anterior necessario → setState com funcao
this.setState(prev => ({ count: prev.count + 1 }))App simples:
app/server/
├── controllers/ # Logica de negocio
└── routes/ # Endpoints HTTP
App complexa:
app/server/
├── controllers/ # Orquestram services
├── services/ # Logica de negocio complexa
├── repositories/ # Acesso a dados
├── routes/ # Endpoints HTTP
└── live/ # Componentes real-time
Antes de finalizar qualquer implementacao, verificar:
- Todas as rotas tem
responseschema definido - Controllers retornam
{ success: true/false, ... } - Tipos usam
as constonde necessario - Nenhum arquivo em
core/foi modificado - Path aliases usados (sem
../../../) - Live Components tem
static componentNameestatic defaultState -
declareusado para propriedades de state - Erros tratados com classes de erro do framework
-
bun run devfunciona apos as mudancas -
bunx tsc --noEmitpassa sem erros
Para detalhes especificos, consultar:
- INDEX.md - Hub de navegacao
- Routes & Eden Treaty - APIs type-safe
- Controllers - Logica de negocio
- Live Components - WebSocket real-time
- Live Rooms - Multi-usuario
- Live Auth - Autenticacao em componentes
- REST Auth - Autenticacao REST
- Config System - Configuracao declarativa
- Anti-Patterns - O que NAO fazer
- CLI Commands - Todos os comandos
- Plugin Hooks - Hooks disponiveis
- Troubleshooting - Problemas comuns