Preview — Jelou Functions está en fase de vista previa. La API y el comportamiento pueden cambiar sin previo aviso.
¿Cuándo usar app() vs define()?
Patrón Cuándo usarlo define()Una sola operación con un endpoint HTTP app()Varias herramientas relacionadas que quieres desplegar juntas
Si tu proyecto tiene un solo handler, usa define(). Si necesitas agrupar múltiples operaciones — por ejemplo, enviar y leer emails desde el mismo servicio — usa app().
Ejemplo básico
import { app , define , z } from "@jelou/functions" ;
export default app ({
config: { cors: { origin: "*" }, timeout: 30_000 } ,
tools: {
enviarEmail: define ({
description: "Envía un correo electrónico" ,
input: z . object ({ to: z . string (), subject: z . string (), body: z . string () }),
config: { timeout: 5_000 },
handler : async ( input ) => ({ sent: true }),
}),
leerBandeja: define ({
description: "Lee los mensajes de la bandeja de entrada" ,
input: z . object ({ limit: z . coerce . number (). default ( 10 ) }),
handler : async ( input ) => ({ messages: [] }),
}),
} ,
}) ;
Prueba local
Inicia el servidor con jelou dev y prueba cada tool en su ruta:
curl /enviar-email
Respuesta 200
curl -X POST http://localhost:3000/enviar-email \
-H "Content-Type: application/json" \
-d '{"to": "[email protected] ", "subject": "Hola", "body": "Bienvenida"}'
curl /leer-bandeja
Respuesta 200
curl http://localhost:3000/leer-bandeja?limit= 5
Si envías un campo inválido, recibes un 400 con detalles:
curl con input inválido
Respuesta 400
curl -X POST http://localhost:3000/enviar-email \
-H "Content-Type: application/json" \
-d '{"to": ""}'
API
import { app } from "@jelou/functions" ;
const edgeApp = app ({
config: { cors: { origin: "*" }, timeout: 30_000 },
tools: {
/* ... tus define() aquí ... */
},
});
export default edgeApp ;
Firma 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 Predeterminado Descripción corsobject{ origin: "*" }Configuración CORS global timeoutnumber30000Timeout en ms para todos los tools methodsstring[]["GET","POST","PUT","PATCH","DELETE"]Métodos HTTP permitidos mcpbooleantrueRegistrar tools en el servidor MCP
Rutas auto-generadas
Tus keys del objeto tools se convierten automáticamente a kebab-case para generar las rutas HTTP:
Key Ruta enviarEmail/enviar-emailleerBandeja/leer-bandejaMiTool/mi-tool
Para personalizar una ruta, usa config.path en el define() individual:
tools : {
enviarEmail : define ({
config: { path: "/emails/enviar" },
handler : async ( input ) => ({ sent: true }),
}),
}
Combinación de config
La configuración global de app() se aplica a todos los tools. Cada define() puede sobreescribir valores específicos:
Campo Comportamiento timeoutEl tool sobreescribe el global methodsEl tool sobreescribe el global corsEl tool sobreescribe el global mcpEl tool sobreescribe el global pathSiempre por tool cronSiempre por tool
export default app ({
config: { timeout: 30_000 , cors: { origin: "*" } } ,
tools: {
rapido: define ({
config: { timeout: 5_000 },
handler : async () => ({ ok: true }),
}),
lento: define ({
handler : async () => ({ ok: true }),
}),
} ,
}) ;
En este ejemplo, rapido tiene un timeout de 5 segundos y lento hereda los 30 segundos globales. Ambos comparten la configuración CORS.
Endpoint de salud
En modo app, /__health retorna información de todos los tools:
{
"mode" : "app" ,
"tools" : [
{
"key" : "enviarEmail" ,
"name" : "Enviar Email" ,
"description" : "Envía un correo electrónico" ,
"path" : "/enviar-email" ,
"cron" : []
},
{
"key" : "leerBandeja" ,
"name" : "Leer Bandeja" ,
"description" : "Lee los mensajes de la bandeja de entrada" ,
"path" : "/leer-bandeja" ,
"cron" : []
}
]
}
Guardas de tipo
Usa isEdgeApp() e isEdgeFunction() para identificar el tipo de export en runtime:
import { isEdgeApp , isEdgeFunction } from "@jelou/functions" ;
isEdgeApp ( module ); // true si fue creado con app()
isEdgeFunction ( module ); // true si fue creado con define()
Cada tool dentro de app() puede tener sus propios schedules cron independientes. Las peticiones cron se envían a la ruta específica de cada tool.
export default app ({
tools: {
limpiezaDiaria: define ({
description: "Limpia registros obsoletos" ,
input: z . object ({}),
config: { cron: [{ expression: "0 3 * * *" , timezone: "UTC" }] },
handler : async ( _input , ctx ) => {
if ( ! ctx . isCron ) return { skipped: true };
return { cleaned: true };
},
}),
sincronizacionHoraria: define ({
description: "Sincroniza datos externos" ,
input: z . object ({}),
config: { cron: [{ expression: "0 * * * *" }] },
handler : async ( _input , ctx ) => {
if ( ! ctx . isCron ) return { skipped: true };
return { synced: true };
},
}),
} ,
}) ;
El límite de 10 schedules cron es agregado entre todos los tools de un app(). Si limpiezaDiaria tiene 3 y sincronizacionHoraria tiene 4, quedan 3 disponibles.
Un solo servidor MCP en /mcp registra automáticamente todos los tools del app(). Para excluir un tool del registro MCP, usa mcp: false en su config:
export default app ({
tools: {
toolPublico: define ({
description: "Visible para MCP" ,
input: z . object ({}),
handler : async () => ({}),
}),
toolInterno: define ({
description: "Solo HTTP" ,
input: z . object ({}),
config: { mcp: false },
handler : async () => ({}),
}),
} ,
}) ;
En este ejemplo, los agentes IA solo descubren toolPublico vía MCP. toolInterno solo es accesible por HTTP directo en su ruta /tool-interno.
Rutas expuestas (modo app)
Ruta Descripción /__healthHealth check con lista de tools /mcpServidor MCP unificado (a menos que config.mcp: false global) /<tool-key>Ruta de cada tool (kebab-case o config.path personalizado)
Problemas comunes
Siguientes pasos