Pular para o conteúdo principal
Preview — Jelou Functions está em preview. A API e o comportamento podem mudar sem aviso prévio.

Quando usar app() vs define()?

PadrãoQuando 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

index.ts
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 -X POST http://localhost:3000/send-email \
  -H "Content-Type: application/json" \
  -d '{"to": "[email protected]", "subject": "Hello", "body": "Welcome"}'
curl http://localhost:3000/read-inbox?limit=5
Se você enviar um campo inválido, recebe um 400 com detalhes:
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;
}
CampoTipoPadrãoDescriçã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:
ChaveRota
sendEmail/send-email
readInbox/read-inbox
MyTool/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:
CampoComportamento
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()

Cron multi-tool

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.

MCP multi-tool

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)

RotaDescriçã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

Próximos passos