Preview — Jelou Functions está em preview. A API e o comportamento podem mudar sem aviso prévio.
Quando usar app() vs define()?
Padrão Quando usar define()Uma única operação com um endpoint HTTP app()Múltiplas ferramentas relacionadas que você quer fazer deploy juntas
Se o seu projeto tem um único handler, use define(). Se você precisa agrupar múltiplas operações — por exemplo, enviar e ler e-mails do mesmo serviço — use app().
Exemplo básico
import { app , define , z } from "@jelou/functions" ;
export default app ({
config: { cors: { origin: "*" }, timeout: 30_000 } ,
tools: {
sendEmail: define ({
description: "Sends an email" ,
input: z . object ({ to: z . string (), subject: z . string (), body: z . string () }),
config: { timeout: 5_000 },
handler : async ( input ) => ({ sent: true }),
}),
readInbox: define ({
description: "Reads inbox messages" ,
input: z . object ({ limit: z . coerce . number (). default ( 10 ) }),
handler : async ( input ) => ({ messages: [] }),
}),
} ,
}) ;
Testes locais
Inicie o servidor com jelou dev e teste cada ferramenta na sua rota:
curl /send-email
Response 200
curl -X POST http://localhost:3000/send-email \
-H "Content-Type: application/json" \
-d '{"to": "[email protected] ", "subject": "Hello", "body": "Welcome"}'
curl /read-inbox
Response 200
curl http://localhost:3000/read-inbox?limit= 5
Se você enviar um campo inválido, recebe um 400 com detalhes:
curl with invalid input
Response 400
curl -X POST http://localhost:3000/send-email \
-H "Content-Type: application/json" \
-d '{"to": ""}'
API
import { app } from "@jelou/functions" ;
const edgeApp = app ({
config: { cors: { origin: "*" }, timeout: 30_000 },
tools: {
/* ... your define() here ... */
},
});
export default edgeApp ;
Assinatura de tipo:
function app ( options : {
config ?: AppConfig ;
tools : Record < string , AnyEdgeFunction >;
}) : EdgeApp ;
AppConfig
interface AppConfig {
cors ?: {
origin ?: string | string [];
methods ?: string [];
headers ?: string [];
credentials ?: boolean ;
maxAge ?: number ;
};
timeout ?: number ;
methods ?: string [];
mcp ?: boolean ;
}
Campo Tipo Padrão Descrição corsobject{ origin: "*" }Configuração global de CORS timeoutnumber30000Timeout em ms para todas as ferramentas methodsstring[]["GET","POST","PUT","PATCH","DELETE"]Métodos HTTP permitidos mcpbooleantrueRegistrar ferramentas no servidor MCP
Rotas geradas automaticamente
As chaves do seu objeto tools são automaticamente convertidas para kebab-case para gerar rotas HTTP:
Chave Rota sendEmail/send-emailreadInbox/read-inboxMyTool/my-tool
Para personalizar uma rota, use config.path no define() individual:
tools : {
sendEmail : define ({
config: { path: "/emails/send" },
handler : async ( input ) => ({ sent: true }),
}),
}
Combinação de config
A config global do app() se aplica a todas as ferramentas. Cada define() pode sobrescrever valores específicos:
Campo Comportamento timeoutFerramenta sobrescreve o global methodsFerramenta sobrescreve o global corsFerramenta sobrescreve o global mcpFerramenta sobrescreve o global pathSempre por ferramenta cronSempre por ferramenta
export default app ({
config: { timeout: 30_000 , cors: { origin: "*" } } ,
tools: {
fast: define ({
config: { timeout: 5_000 },
handler : async () => ({ ok: true }),
}),
slow: define ({
handler : async () => ({ ok: true }),
}),
} ,
}) ;
Neste exemplo, fast tem um timeout de 5 segundos e slow herda os 30 segundos globais. Ambas compartilham a configuração de CORS.
Endpoint de health
No modo app, /__health retorna informações sobre todas as ferramentas:
{
"mode" : "app" ,
"tools" : [
{
"key" : "sendEmail" ,
"name" : "Send Email" ,
"description" : "Sends an email" ,
"path" : "/send-email" ,
"cron" : []
},
{
"key" : "readInbox" ,
"name" : "Read Inbox" ,
"description" : "Reads inbox messages" ,
"path" : "/read-inbox" ,
"cron" : []
}
]
}
Type guards
Use isEdgeApp() e isEdgeFunction() para identificar o tipo de export em tempo de execução:
import { isEdgeApp , isEdgeFunction } from "@jelou/functions" ;
isEdgeApp ( module ); // true if created with app()
isEdgeFunction ( module ); // true if created with define()
Cada ferramenta dentro de app() pode ter seus próprios schedules de cron independentes. As requisições de cron são enviadas para a rota específica de cada ferramenta.
export default app ({
tools: {
dailyCleanup: define ({
description: "Cleans up stale records" ,
input: z . object ({}),
config: { cron: [{ expression: "0 3 * * *" , timezone: "UTC" }] },
handler : async ( _input , ctx ) => {
if ( ! ctx . isCron ) return { skipped: true };
return { cleaned: true };
},
}),
hourlySync: define ({
description: "Syncs external data" ,
input: z . object ({}),
config: { cron: [{ expression: "0 * * * *" }] },
handler : async ( _input , ctx ) => {
if ( ! ctx . isCron ) return { skipped: true };
return { synced: true };
},
}),
} ,
}) ;
O limite de 10 schedules de cron é agregado entre todas as ferramentas de um app(). Se dailyCleanup tiver 3 e hourlySync tiver 4, só restam 3 disponíveis.
Um único servidor MCP em /mcp registra automaticamente todas as ferramentas do app(). Para excluir uma ferramenta do registro MCP, use mcp: false na sua config:
export default app ({
tools: {
publicTool: define ({
description: "Visible to MCP" ,
input: z . object ({}),
handler : async () => ({}),
}),
internalTool: define ({
description: "HTTP only" ,
input: z . object ({}),
config: { mcp: false },
handler : async () => ({}),
}),
} ,
}) ;
Neste exemplo, os agentes de IA só descobrem publicTool via MCP. internalTool só é acessível via HTTP direto na sua rota /internal-tool.
Rotas expostas (modo app)
Rota Descrição /__healthHealth check com lista de ferramentas /mcpServidor MCP unificado (a menos que config.mcp: false globalmente) /<tool-key>Rota de cada ferramenta (kebab-case ou config.path personalizado)
Problemas comuns
As chaves de tools são convertidas para kebab-case . Se sua chave é sendEmail, a rota é /send-email, não /sendEmail. Verifique as rotas reais com o health check: curl http://localhost:3000/__health
A validação do schema é executada antes do handler. Se você receber um 400, verifique o array details: curl -X POST http://localhost:3000/send-email \
-H "Content-Type: application/json" \
-d '{"to": 12345}'
Cada entrada em details indica o campo (path), o que era esperado (message) e o código Zod (code). Se uma ferramenta tem config: { mcp: false }, ela não é registrada no servidor MCP. Verifique quais ferramentas estão expostas: curl http://localhost:3000/mcp
Se uma ferramenta estiver faltando, verifique se ela não tem mcp: false na sua config.
Próximos passos