Especificación de funciones¶
Estado verificado al 28 de marzo de 2026. Nota de runtime: FastFN resuelve dependencias y build por función según el runtime: Python usa
requirements.txt, Node usapackage.json, PHP instala desdecomposer.jsoncuando existe, y Rust compila handlers concargo. Enfastfn dev --nativenecesitas runtimes y herramientas del host;fastfn devdepende de un daemon de Docker activo.
Nombres y rutas¶
- nombre (flat):
^[a-zA-Z0-9_-]+$ - nombre (namespaced):
<segmento>/<segmento>/.../<nombre>donde cada segmento cumple^[a-zA-Z0-9_-]+$ - versión:
^[a-zA-Z0-9_.-]+$ - rutas públicas (por defecto):
/<name>(flat)/<segmento>/<segmento>/.../<nombre>(namespaced — la estructura de directorios se mapea a rutas, estilo Next.js)/<name>@<version>
Los nombres con namespace mapean la estructura de directorios directamente a rutas URL:
| Path en disco (bajo carpeta runtime) | Nombre de función | Ruta |
|---|---|---|
hello/handler.py |
hello |
/hello |
alice/hello/handler.py |
alice/hello |
/alice/hello |
api/v1/users/handler.py |
api/v1/users |
/api/v1/users |
Casos de uso: plataformas multi-tenant (alice/hello, bob/greet), namespacing de APIs (api/v1/users), agrupación organizacional (team/service/handler).
Estado de runtimes¶
Implementados y ejecutables hoy:
pythonnodephplua(corre in-process dentro de OpenResty — no necesita daemon externo)
Experimentales (opt-in via FN_RUNTIMES):
rustgo
Root de funciones configurable¶
FastFN descubre funciones escaneando un directorio del filesystem.
Setup común (recomendado):
- Crear
functions/en tu repo. - Correr
fastfn dev functions(o setear"functions-dir": "functions"enfastfn.json).
En modo portable (Docker), FastFN monta tu carpeta de funciones dentro del contenedor.
Por default usa ghcr.io/misaelzapata/fastfn-runtime:latest.
Si necesitas otra imagen o tag, define FN_RUNTIME_IMAGE antes de correr la CLI.
También puedes controlar discovery con:
FN_RUNTIMES(CSV, ejemplopython,node,php,rust)FN_RUNTIME_SOCKETS(JSON runtime -> socket URI)FN_SOCKET_BASE_DIR(base de sockets si no hay mapa explícito)
Precedencia de runtime (cuando hay colisiones):
- Si el mismo nombre existe en varios runtimes,
/<name>usa el primer runtime enFN_RUNTIMES. - Si
FN_RUNTIMESno está definido, usa orden alfabético de carpetas runtime.
Cableado de procesos runtime¶
El cableado global de runtimes vive fuera de fn.config.json.
Controles principales:
FN_RUNTIMESpara habilitar runtimesruntime-daemonsoFN_RUNTIME_DAEMONSpara definir counts por runtime externoFN_RUNTIME_SOCKETSpara pasar un mapa explícito de socketsruntime-binariesoFN_*_BINpara elegir el ejecutable del host usado por cada runtime o herramienta
Reglas importantes:
luacorre dentro de OpenResty, así que los counts paraluase ignoran.FN_RUNTIME_SOCKETSacepta string o array por runtime.- Si defines
FN_RUNTIME_SOCKETS, gana sobre los sockets generados desderuntime-daemons. - FastFN elige un ejecutable por clave. Si ejecutas tres daemons de Python, los tres usan el mismo
FN_PYTHON_BIN.
Ejemplo:
{
"runtime-daemons": {
"node": 3,
"python": 3
},
"runtime-binaries": {
"python": "python3.12",
"node": "node20"
}
}
Archivos de codigo¶
Archivos de entrada por runtime (en orden de resolucion):
| Runtime | Candidatos (en orden) |
|---|---|
| Python | handler.py → main.py |
| Node | handler.js → handler.ts → index.js → index.ts |
| PHP | handler.php → index.php |
| Lua | handler.lua → main.lua → index.lua |
| Go | handler.go → main.go |
| Rust | handler.rs |
Convencion
Usa handler.<ext> como default. El nombre coincide con el callable por defecto (handler(event)) y mantiene el contrato publico consistente entre runtimes.
Resolucion de handler (2 pasos)¶
La resolucion del handler funciona en dos pasos: seleccion de archivo y luego seleccion del callable.
Paso 1 — Seleccion de archivo:
entrypointexplicito enfn.config.json(e.g.src/my_handler.py).- Rutas por archivo (estilo Next.js):
<method>.<tokens>.<ext>o<method>.<ext>. - Archivos de entrada por defecto en orden fijo por runtime (ver tabla arriba).
No existe fallback a "el primer archivo del directorio". Si ninguna regla coincide, la carpeta no expone endpoint.
Paso 2 — Seleccion del callable:
- Default:
handler(event) - Override con
fn.config.json→invoke.handler(debe ser identificador valido:^[a-zA-Z_][a-zA-Z0-9_]*$). - Python: si el callable
handlerno existe, FastFN buscamain(event)como fallback. - Cloudflare Workers adapter: si
invoke.adapterescloudflare-worker, FastFN busca primerofetchantes del nombre configurado.
| Campo | Alcance | Ejemplo | Efecto |
|---|---|---|---|
entrypoint |
Seleccion de archivo | "src/api.py" |
Carga src/api.py en vez de archivos por convencion |
invoke.handler |
Seleccion de callable | "process_request" |
Llama process_request(event) en vez de handler(event) |
Inyeccion directa de parametros¶
Cuando una ruta tiene segmentos dinamicos (e.g. [id], [...slug]), los parametros extraidos se inyectan en el handler. El mecanismo varia por runtime:
| Runtime | Metodo de inyeccion | Firma de ejemplo |
|---|---|---|
| Python | inspect.signature → kwargs nombrados |
def handler(event, id): |
| Node | Segundo argumento (objeto desestructurado) | async (event, { id }) => |
| PHP | ReflectionFunction → segundo argumento |
function handler($event, $params) |
| Lua | Siempre segundo argumento (tabla) | function handler(event, params) |
| Go | Merge en event map bajo clave params |
event["params"]["id"] |
| Rust | Merge en event Value bajo clave params |
event["params"]["id"] |
Los parametros siempre estan disponibles en event.params sin importar el runtime. La inyeccion directa es una conveniencia que evita extraccion manual.
Modos de discovery¶
FastFN usa tres modos de discovery de rutas. El modo sale de la estructura real de la carpeta, no de una blacklist ad hoc de nombres.
1. Arbol puro de rutas por archivos¶
Si una carpeta no define un entrypoint único, FastFN trata los archivos compatibles como rutas públicas.
Ejemplos:
users/index.js->GET /usersusers/[id].js->GET /users/:idadmin/post.users.[id].py->POST /admin/users/:idhello.js->GET /hello- Se permite exactamente un prefijo de método por archivo.
get.post.items.jses ambiguo y FastFN lo ignora con warning.
Los helpers privados en este modo deben usar prefijo _:
_shared.js_helpers.py_csv.php
Esos archivos quedan privados y no aparecen en OpenAPI ni en el catálogo.
2. Single-entry root¶
Si una carpeta declara un único entrypoint de función, esa carpeta se comporta como una función single-entry, similar a un directorio Lambda.
Una carpeta entra en este modo cuando tiene:
fn.config.jsonconentrypointexplícito, o- un archivo canónico como
handler.*,main.*oindex.*(ver tabla por runtime arriba)
Ejemplos:
payments/handler.js->GET/POST/DELETE /paymentsrisk-score/main.py->GET /risk-score
En este modo, los archivos hermanos son módulos internos por defecto:
payments/core.jsse puede importar desdehandler.js, pero no se publica como/payments/corerisk-score/model.pyse puede importar desdemain.py, pero no se publica como/risk-score/model
3. Subárbol mixto¶
Dentro de una función single-entry, las subcarpetas todavía pueden exponer subrutas file-based explícitas.
Ejemplos:
shop/handler.js->/shopshop/admin/index.js->/shop/adminshop/admin/get.health.js->GET /shop/admin/health
Dentro de un subárbol mixto solo se publican archivos de ruta explícitos:
index.*,handler.*,main.*- archivos con prefijo de método como
get.*,post.*,put.*,patch.*,delete.* - archivos dinámicos como
[id].*,[...slug].*,[[...slug]].*
Helpers planos como core.js, shared.py, lib.php, common.rs o utils.go quedan privados.
Estructura recomendada (relativa a FN_FUNCTIONS_ROOT)¶
<FN_FUNCTIONS_ROOT>/
hello/
handler.py # GET /hello
users/
get.js # GET /users
[id]/
get.py # GET /users/:id
delete.py # DELETE /users/:id
Categorias de layout
- Recomendado: Path-neutral (
hello/handler.py,users/get.js). Usado en tutoriales yfastfn init. - Soportado (compatibilidad): Agrupado por runtime (
python/hello/handler.py,node/echo/handler.js). Util para monorepos con muchos runtimes. Discovery usaFN_NAMESPACE_DEPTH(default3, max5). - No recomendado: Mezclar ambos layouts en el mismo root de funciones. Discovery funciona pero la precedencia de rutas se vuelve dificil de razonar.
FastFN también soporta árboles agrupados por runtime para monorepos y repos mixtos grandes. fastfn init ahora genera por defecto directorios path-neutral de función única, así que toma el layout agrupado por runtime como una opción de organización y no como el camino principal que enseña esta documentación.
Namespaces anidados (estilo Next.js)¶
Los directorios anidados bajo tu árbol de funciones se mapean directamente a rutas URL:
hello/handler.py # GET /hello
api/
v1/
users/handler.py # GET /api/v1/users
orders/handler.py # GET /api/v1/orders
alice/
dashboard/handler.py # GET /alice/dashboard
El discovery recursa en directorios que no contienen un single-entry root, tratándolos como segmentos de namespace. Un directorio que contiene un single-entry root (handler.py, handler.js, main.py, entrypoint explícito, etc.) se trata como una función. Las subcarpetas descendientes pueden seguir exponiendo rutas file-based explícitas, pero los módulos helper hermanos quedan privados.
Límite de profundidad: FN_NAMESPACE_DEPTH controla cuántos niveles profundiza el scanner para árboles agrupados por runtime en modo compatibilidad (default 3, max 5). Por ejemplo, con depth 3 el path python/a/b/c/handler.py se descubre como función a/b/c → ruta /a/b/c.
Límites de Profundidad
El ajuste FN_NAMESPACE_DEPTH aplica a directorios agrupados por runtime en modo compatibilidad (por ejemplo python/, node/).
Las rutas zero-config basadas en archivos usan un límite de profundidad fijo separado de 6 niveles.
Los paths que exceden ese límite fijo zero-config se omiten con warning de discovery, en lugar de fallar silenciosamente.
No existe fallback a "el primer archivo del directorio". Si ninguna regla de entrypoint o ruta explícita coincide, esa carpeta no expone un endpoint público.
Si una carpeta contiene múltiples entry files compatibles entre runtimes, FastFN resuelve de forma determinista en el orden go, lua, node, php, python, rust y emite un warning con los matches ignorados.
El discovery de namespaces y file routes también avisa cuando un segmento cae fuera del set ASCII soportado o cuando la ruta pública normalizada colisiona con prefijos reservados como /_fn o /console.
Archivos opcionales por funcion/version:
fn.config.jsonfn.env.jsonrequirements.txt(Python)package.json,package-lock.json(Node)composer.json,composer.lock(PHP, opcional)Cargo.toml,Cargo.lock(Rust, opcional)
Ejemplos minimos de handler (mismo contrato)¶
Todos consumen event. El contrato portable recomendado es devolver {status, headers, body}.
Python¶
import json
def handler(event):
name = (event.get("query") or {}).get("name", "world")
return {
"status": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps({"hello": name}),
}
Node¶
exports.handler = async (event) => {
const query = event.query || {};
const name = query.name || 'world';
return {
status: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ hello: name }),
};
};
PHP¶
<?php
function handler($event) {
$query = $event['query'] ?? [];
$name = $query['name'] ?? 'world';
return [
'status' => 200,
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode(['hello' => $name]),
];
}
Go¶
El handler debe ser una funcion a nivel de paquete llamada handler (o Handler).
El daemon lo envuelve y compila — no uses func main() con stdin/stdout.
package main
import "encoding/json"
func handler(event map[string]interface{}) interface{} {
body, _ := json.Marshal(map[string]interface{}{"hello": "world"})
return map[string]interface{}{
"status": 200,
"headers": map[string]string{"Content-Type": "application/json"},
"body": string(body),
}
}
Rust¶
El archivo de entrada debe ser handler.rs en la raiz del directorio de funcion (no dentro de src/).
El handler debe ser una funcion publica llamada handler. El daemon lo compila — no uses fn main() con stdin/stdout.
use serde_json::{json, Value};
pub fn handler(event: Value) -> Value {
let name = event
.get("query")
.and_then(|q| q.get("name"))
.and_then(|n| n.as_str())
.unwrap_or("mundo");
json!({
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": json!({ "hello": name }).to_string()
})
}
Respuesta sencilla (atajos por runtime)¶
El contrato canonico portable sigue siendo:
{ status, headers, body }- o binario
{ status, headers, is_base64, body_base64 }
Soporte de atajos por runtime:
| Runtime | Soporte | Formas aceptadas | Notas |
|---|---|---|---|
| Node | si | dict/object, string, number, array | Valores sin envelope se envuelven como JSON body con status 200 |
| Python | si | dict, tuple (body, status, headers), (body, status), (body,), dict/list plano |
Dict sin clave status se envuelve como JSON 200. statusCode aceptado como alias de status. bytes en body se codifica como base64 automaticamente. |
| PHP | si | array, object, primitivo | Se envuelve como JSON body con status 200 |
| Lua | si | table, string, number | Se envuelve como JSON body con status 200 |
| Go | no | — | Requiere envelope explicito { "status", "headers", "body" } |
| Rust | no | — | Requiere envelope explicito { "status", "headers", "body" } |
Respuestas binarias: usa is_base64: true y proporciona el contenido en body_base64. Python detecta bytes en body y codifica como base64 automaticamente.
Validacion de status: todos los runtimes validan codigos de estado en rango 100-599.
Para paridad entre runtimes, usa respuesta explicita en ejemplos compartidos.
fn.config.json¶
Campos clave:
timeout_msmax_concurrencymax_body_bytesgroup(opcional)shared_deps(opcional)edge(opcional, passthrough tipo edge)include_debug_headersschedule(cron simple por intervalo, opcional)invoke.methodsinvoke.handler(opcional, nombre de función exportada dentro del archivo resuelto; defaulthandler)invoke.routes(mapeo opcional de endpoint publico)invoke.force-url(opcional, sitruepuede sobrescribir una URL ya mapeada)invoke.adapter(Beta, Node/Python): modo de compatibilidad (native,aws-lambda,cloudflare-worker)home(opcional, overlay por carpeta/root):home.routeohome.function: path interno a ejecutar como home.home.redirect: URL/path para redirección home (302).assets(opcional, solo en root) para montar una carpeta estática en/:assets.directory: carpeta relativa a servir, por ejemplopublicodist.assets.not_found_handling:404osingle-page-application.assets.run_worker_first: si estrue, las rutas de funciones ganan antes que los assets.invoke.summaryinvoke.queryinvoke.body
Ejemplo:
{
"group": "demos",
"shared_deps": ["common_http"],
"timeout_ms": 1500,
"max_concurrency": 10,
"max_body_bytes": 1048576,
"include_debug_headers": false,
"invoke": {
"handler": "main",
"adapter": "native",
"force-url": false,
"methods": ["GET", "POST"],
"routes": ["/api/mi-funcion"],
"summary": "Mi funcion",
"query": {"name": "World"},
"body": ""
},
"home": {
"route": "/api/mi-funcion"
}
}
Notas:
invoke.handlerpermite estilo Lambda con nombre de handler custom (main,run, etc.).invoke.handlerelige el símbolo a invocar dentro del archivo ya resuelto; no cambia la selección del archivo.- En runtimes Node y Python, esa función debe existir/exportarse en el mismo archivo.
- Python además acepta
main(req)cuando no existehandler. invoke.routeses opcional.- Si existe, cada ruta debe ser absoluta (por ejemplo
/api/mi-funcion). - En layout por archivos,
home.routepermite aliasar la raíz de una carpeta (por ejemplo/portal) hacia otra ruta detectada en esa carpeta (por ejemplo/portal/dashboard). - En
fn.config.jsonraíz,home.route/home.redirectpermite override de/sin editar Nginx. - En
fn.config.jsonraíz también puedes definirassetspara que FastFN sirva una carpeta pública directamente desde el gateway, estilo Cloudflare. assets.directorydebe ser un path relativo seguro dentro del root de funciones y la carpeta debe existir.assetses root-only en v1; losfn.config.jsonanidados no crean mounts públicos adicionales./_fn/*y/console/*siguen reservados y nunca se sirven desdeassets.assetsno expone carpetas hermanas de funciones: solo se sirve el directorio configurado y FastFN bloquea dotfiles e intentos de traversal.- Prefijos reservados no permitidos (
/_fn,/console,/docs). - Si dos rutas colisionan con la misma prioridad de discovery, FastFN no conserva ninguna de las dos para esa URL, la registra como conflicto y responde
409hasta que la desambigües. - Por defecto, FastFN no sobrescribe silenciosamente un mapeo de URL existente.
- Usa
invoke.force-url: truesolo cuando realmente quieres que esta función se quede con una ruta (por ejemplo, durante una migración). - Los configs por versión (por ejemplo
mi-fn/v2/fn.config.json) no pueden \"tomar\" una URL existente por sí solos; usaFN_FORCE_URL=1si necesitas que una ruta versionada gane. - Override global: setea
FN_FORCE_URL=1(ofastfn dev --force-url) para tratar todas las rutas config/policy como forced.
Assets públicos en root¶
Usa assets en el fn.config.json raíz cuando quieres que FastFN sirva una carpeta desde / sin pasar por un handler.
Ejemplo:
{
"assets": {
"directory": "public",
"not_found_handling": "single-page-application",
"run_worker_first": false
}
}
Comportamiento:
GETyHEADse sirven directo desde el gateway./y las URLs de directorio resuelven aindex.html.- En modo
single-page-application, los misses de navegación vuelven aindex.html. - Los paths tipo archivo inexistentes, como
/missing.js, siguen devolviendo404. - Una carpeta de assets vacía no crea una home sintética. Si no hay asset real, ni home explícito, ni rutas descubiertas,
/devuelve404. - Si
run_worker_firstestrue, FastFN prueba primero las rutas de funciones y solo cae a assets cuando no hubo match. - Esto vuelve first-class a carpetas tipo
public/odist/sin perder handlers normales. - Mira los demos ejecutables en
examples/functions/assets-static-first,examples/functions/assets-spa-fallbackyexamples/functions/assets-worker-first.
keep_warm¶
La configuración keep_warm indica al scheduler que mantenga la función cargada y lista.
enabled: activa el scheduler de keep-warm.min_warm: cantidad mínima a mantener caliente.ping_every_seconds: intervalo entre heartbeats.idle_ttl_seconds: cuánto tiempo puede quedar ociosa antes de enfriarse.
worker_pool¶
worker_pool es la forma más simple de controlar una función sin cambiar sus rutas.
Detalle importante del modelo:
worker_pooles por función.runtime-daemonses por runtime y vive enfastfn.jsono en variables de entorno, no enfn.config.json.- OpenResty/Lua aplica
worker_pool.max_workers,max_queuey timeouts de cola antes de entrar al runtime. - Después de admitir la request, el gateway elige un socket sano del runtime. Si el runtime tiene más de un socket, la selección es
round_robin.
Ejemplo:
{
"worker_pool": {
"enabled": true,
"max_workers": 3,
"max_queue": 6,
"queue_timeout_ms": 5000,
"idle_ttl_seconds": 300,
"overflow_status": 429
}
}
Campos principales:
enabled: activa la ejecución con pool para esa función.max_workers: máximo de ejecuciones activas admitidas para la función.max_queue: requests extra permitidas en cola cuando todos los workers están ocupados.queue_timeout_ms: cuánto puede esperar una request en cola antes de devolveroverflow_status.idle_ttl_seconds: cuánto tiempo permanecen vivos los workers ociosos antes de limpiarse.overflow_status: estado de respuesta al desbordar cola o agotar espera (429o503).min_warm: mantiene algunos workers ya creados cuando el runtime lo soporta.queue_poll_ms: Frecuencia de verificacion de capacidad disponible cuando un request esta en cola (ajuste interno, raramente necesita cambiarse).
Comportamiento actual por runtime:
| Runtime | Routing multi-daemon | Fan-out interno del runtime |
|---|---|---|
| Node | soportado | además usa workers hijos dentro de node-daemon.js |
| Python | soportado | la ejecución sigue dependiendo del comportamiento del daemon Python |
| PHP | soportado | el despacho sucede mediante el lanzador PHP |
| Rust | soportado | el despacho sucede mediante el binario compilado |
| Lua | no aplica | corre dentro de OpenResty |
El snapshot de benchmark verificado el 14 de marzo de 2026 mostró resultados dependientes del runtime: algunos mejoraron mucho con más daemons, otros poco, y un path native de PHP llegó a empeorar antes de un fix posterior.
Usa la página canónica de benchmarks para ver los números exactos y los artefactos crudos antes de activar más daemons en todos los runtimes:
Adaptadores de invocacion¶
El campo invoke.adapter en fn.config.json controla la convencion de llamada del handler. Default: native.
| Adaptador | Firma del handler | Disponible para |
|---|---|---|
native |
handler(event) |
Todos los runtimes |
aws-lambda |
handler(event, context) |
Python, Node |
cloudflare-worker |
fetch(request, env, ctx) |
Python, Node |
Aliases: lambda, apigw-v2, api-gateway-v2 → aws-lambda. worker, workers → cloudflare-worker.
Adaptador AWS Lambda:
eventse transforma al formato API Gateway v2.contextproveegetRemainingTimeInMillis(),done(),fail(),succeed().- El valor de retorno se normaliza de vuelta al envelope FastFN.
Adaptador Cloudflare Workers:
- Busqueda de handler: FastFN busca primero un export
fetch, luego cae al nombre configurado. requestprovee.text(),.json(),.url,.method,.headers.envcontiene las variables de entorno de la funcion desdefn.env.json.ctxproveewaitUntil()ypassThroughOnException().ctx.waitUntil()corre como trabajo best-effort en background: no retrasa la respuesta HTTP y los awaitables rechazados quedan logueados como eventos de runtime.
Nota Node + Lambda callback:
- En modo
aws-lambda, Node soporta tanto handlers async como handlers con callback (event, context, callback).
Config edge passthrough (edge)¶
Si quieres un comportamiento estilo Cloudflare Workers (el handler devuelve un proxy y el gateway hace el request saliente), habilítalo por función en fn.config.json:
{
"edge": {
"base_url": "https://api.example.com",
"allow_hosts": ["api.example.com"],
"allow_private": false,
"max_response_bytes": 1048576
}
}
Después el handler puede devolver { "proxy": { ... } }. Ver el contrato completo en: Contrato Runtime.
Gestion de dependencias (auto-install + inferencia)¶
FastFN resuelve dependencias o build por carpeta de función y agrega inferencia autónoma para Python/Node.
Modelo de resolucion:
- Python, Node y PHP usan manifiestos locales por función (
requirements.txt,package.json,composer.json). - Los handlers Rust se compilan con
cargodentro de un workspace.rust-build/por función. - FastFN no busca automaticamente dependencias en la raiz del repo.
- Para reutilizacion entre muchas funciones, usa
shared_deps. - Python y Node dejan trazabilidad de resolucion en
<function_dir>/.fastfn-deps-state.json. - PHP y Rust hoy resuelven/installan/build-ean directo sin ese archivo de estado por funcion.
Archivos que FastFN escribe por runtime¶
| Runtime | Archivo de estado | Lock/snapshot | Directorio de deps | Directorio de build |
|---|---|---|---|---|
| Python | .fastfn-deps-state.json |
requirements.lock.txt (informativo, output de pip freeze) |
.deps/ |
— |
| Node | .fastfn-deps-state.json |
package-lock.json (funcional, lo usa npm ci) |
node_modules/ |
— |
| PHP | — | — | vendor/ |
— |
| Rust | — | — | — | .rust-build/ |
| Go | — | — | — | .go-build/ |
| Lua | — | — | — | — (in-process, sin deps externas) |
requirements.lock.txt es un snapshot informativo generado por pip freeze. NO se usa para instalacion — solo para auditar lo que se instalo. package-lock.json es funcional — npm ci lo usa para instalaciones deterministas.
Cuando corre la inferencia en Python o Node, FastFN tambien guarda:
infer_backendinference_duration_ms
Python (manifiesto + inferencia)¶
Entradas soportadas:
requirements.txt- hints inline
#@requirements ... - inferencia de imports cuando falta o esta incompleto el manifiesto
Hints inline: FastFN escanea las primeras 30 lineas del handler buscando comentarios #@requirements <paquete> [<paquete>...]. Se combinan con las entradas de requirements.txt.
Comportamiento:
- Si falta
requirements.txty la inferencia resuelve imports, FastFN lo crea automaticamente. - Si
requirements.txtexiste, agrega paquetes faltantes inferidos sin borrar tus pins. - Tras una instalacion exitosa, escribe
requirements.lock.txt(lock informativo en v1).
Toggles:
FN_AUTO_REQUIREMENTS=0desactiva auto-install de Python.FN_AUTO_INFER_PY_DEPS=0desactiva inferencia Python.FN_PY_INFER_BACKEND=native|pipreqselige el backend de inferencia Python.FN_AUTO_INFER_WRITE_MANIFEST=0evita escribir manifiestos desde inferencia.FN_AUTO_INFER_STRICT=1falla si hay imports no resolubles.FN_PREINSTALL_PY_DEPS_ON_START=1preinstala deps al iniciar runtime.
Invalidacion de cache: FastFN calcula una firma con el mtime del handler, mtime del manifiesto e inline requirements. Si la firma coincide con la instalacion previa y .deps/ no esta vacio, reutiliza las dependencias. Timeout de instalacion: 180 segundos.
La inferencia es opcional y normalmente mas lenta que usar un manifiesto explicito porque FastFN puede tener que analizar imports o invocar una herramienta externa.
Para el loop mas rapido y para produccion, prefiere requirements.txt o #@requirements.
La inferencia solo auto-agrega nombres directos de paquete, por ejemplo requests -> requests.
FastFN no mantiene una tabla interna de aliases de imports Python.
Si el nombre del import no coincide con el paquete que instalas (PIL/Pillow, yaml/PyYAML, jwt/PyJWT, etc.), decláralo explícitamente en requirements.txt o con #@requirements.
Cuando lo declaras explícitamente, ese manifiesto pasa a ser la fuente de verdad y los imports alias no resueltos quedan como información, no como bloqueo de instalación.
Notas de backend:
nativees el default y es intencionalmente conservador.pipreqses opt-in y requiere quepipreqsexista en el entorno donde corre el daemon Python.
Node (manifiesto + inferencia)¶
Entradas soportadas:
package.json- inferencia de
import/requirepara dependencias faltantes
Comportamiento:
- Si falta
package.jsony hay imports resolubles, FastFN creapackage.json. - Si existe
package.json, agrega dependencias faltantes inferidas. - Con lockfile usa
npm ci; sin lockfile usanpm install.
Si npm ci falla con lockfile presente, FastFN reintenta con npm install. Timeout de instalacion: 180 segundos.
Toggles:
FN_AUTO_NODE_DEPS=0desactiva auto-install de Node.FN_AUTO_INFER_NODE_DEPS=0desactiva inferencia Node.FN_NODE_INFER_BACKEND=native|detective|require-analyzerelige el backend de inferencia Node.FN_AUTO_INFER_WRITE_MANIFEST=0evita escritura de manifiesto inferido.FN_AUTO_INFER_STRICT=1activa fail-fast en imports no resolubles.FN_PREINSTALL_NODE_DEPS_ON_START=1preinstala deps al iniciar.FN_PREINSTALL_NODE_DEPS_CONCURRENCY=4controla concurrencia de preinstall.
Invalidacion de cache: FastFN calcula una firma con el mtime de package.json y de package-lock.json (o "no-lock" si no existe). Si la firma coincide y node_modules/ existe, reutiliza las dependencias.
La inferencia de Node excluye paquetes que coincidan con nombres de shared_deps para evitar duplicar dependencias compartidas.
La inferencia de Node tambien es opcional y normalmente mas lenta que declarar package.json desde el inicio.
Usa manifiestos explicitos cuando ya conoces las dependencias o cuando quieres el arranque mas corto posible.
Notas de backend:
nativees el default.detectivees opt-in y funciona mejor conrequire(...)estatico.require-analyzeres opt-in y sirve como ayuda de bootstrap mas amplia, pero no reemplaza unpackage.jsonexplicito.
PHP (solo manifiesto en esta fase)¶
- Usa
composer.json(y opcionalmentecomposer.lock) por funcion. - FastFN ejecuta
composer installpor funcion cuando corresponde. - No hay inferencia por imports en PHP en esta fase.
FN_AUTO_PHP_DEPS=0desactiva auto-install de Composer.- PHP hoy no emite
metadata.dependency_resolution.
Rust (build en esta fase)¶
Comportamiento:
- FastFN compila handlers Rust con
cargo build --release. - El runtime prepara un workspace
.rust-build/por función y compila el handler allí. - No hay inferencia por imports para Rust en esta fase.
- El modo native requiere
cargoenPATH. - Rust hoy no emite
metadata.dependency_resolution.
Go (build)¶
- FastFN compila handlers Go con
go builddentro de un workspace.go-build/por funcion. - Si
go.modygo.sumexisten en el directorio de la funcion, se usan para resolucion de modulos. - Timeout de build controlado por
GO_BUILD_TIMEOUT_S(default:180segundos). - El modo native requiere
goenPATH. - Go es experimental y debe habilitarse via
FN_RUNTIMES.
Lua (in-process)¶
Los handlers Lua corren dentro del proceso OpenResty. No hay daemon externo, no hay instalacion de dependencias ni archivos de estado. Los modulos disponibles en el entorno OpenResty (cjson, resty.*) se pueden usar directamente.
Errores estrictos y transparencia¶
- Si la inferencia no resuelve imports (con strict activo), la invocacion falla con error accionable.
- Los fallos de install o build muestran un tail corto de pip/npm/composer/cargo para debug rapido.
GET /_fn/functionexponemetadata.dependency_resolutioncuando el runtime escribe ese estado (hoy sobre todo Python/Node).
Flujo resumido:
- FastFN lee el manifiesto local de la funcion.
- Si el manifiesto ya alcanza, instala desde ahi.
- Si falta o esta incompleto, Python y Node pueden inferir imports y escribir el manifiesto.
- Luego el runtime guarda estado de resolucion y lock info cuando aplica.
- Finalmente ejecuta el handler o compila el binario Rust.
Packs de dependencias compartidas (shared_deps)¶
Si quieres que varias funciones reutilicen la misma instalación de dependencias (por ejemplo un node_modules compartido para Node, o una carpeta de pip compartida para Python), puedes usar packs compartidos.
Los nombres de pack los defines tú, y una función puede combinar sus dependencias locales con uno o más packs compartidos.
En fn.config.json:
Los packs viven dentro del root de funciones (funciona con el volumen default de Docker):
Si tu functions root es runtime-scoped (por ejemplo <root>/python o <root>/node), FastFN también revisa un nivel arriba buscando el mismo layout .fastfn/packs/<runtime>/... para mantener compatibilidad con esa variante.
Ejemplos:
- Pack Python:
<FN_FUNCTIONS_ROOT>/.fastfn/packs/python/qrcode_pack/requirements.txt - Pack Node:
<FN_FUNCTIONS_ROOT>/.fastfn/packs/node/qrcode_pack/package.json - Pack Node TypeScript (esbuild):
<FN_FUNCTIONS_ROOT>/.fastfn/packs/node/ts_pack/package.json
En Node, el pack también puede traer un node_modules/ ya resuelto dentro del propio pack. Si existe package.json, FastFN puede instalar ahí las dependencias del pack.
En runtime:
- la función conserva sus dependencias locales si las tiene
shared_depssuma uno o más roots reutilizables por encima de eso- Python instala en
<pack>/.depsy lo agrega asys.path - Node instala en
<pack>/node_modulesy lo agrega a la resolucion de modulos para esa invocacion - si falta un pack configurado, FastFN falla rápido con un error accionable
Esto no es aislamiento nivel kernel (virtualenv/cargo completo), pero sirve para deduplicar instalaciones de forma simple.
Schedule (cron o intervalo)¶
Puedes adjuntar un schedule a una funcion usando:
every_seconds(intervalo)cron(expresion cron)
Schedule por intervalo (every_seconds)¶
{
"schedule": {
"enabled": true,
"every_seconds": 60,
"method": "GET",
"query": {},
"headers": {},
"body": "",
"context": {}
}
}
Schedule cron (cron)¶
Cron soporta:
- 5 campos:
min hour dom mon dow - 6 campos:
sec min hour dom mon dow - macros:
@hourly,@daily,@midnight,@weekly,@monthly,@yearly,@annually - aliases de mes/dia:
JAN..DEC,SUN..SAT - day-of-week acepta
0..6y tambien7para domingo
{
"schedule": {
"enabled": true,
"cron": "*/5 * * * *",
"timezone": "UTC",
"method": "GET",
"query": {},
"headers": {},
"body": "",
"context": {}
}
}
Timezones:
UTC,Zlocal(default si se omite)-
offsets fijos como
+02:00,-05:00,+0200o-0500 -
El scheduler corre dentro de OpenResty (worker 0).
- Invoca el runtime por unix socket.
- La policy aplica igual (metodos, body, concurrencia, timeout).
- Para correr cada X minutos, usa
every_seconds = X * 60(ejemplo: 15 minutos =>900). - Cuando day-of-month y day-of-week estan restringidos a la vez, el match sigue semantica tipo Vixie (
OR). - Estado del scheduler:
GET /_fn/schedules(next,last,last_status,last_error). - Cuando hay retries pendientes, el snapshot tambien expone
retry_dueyretry_attempt. - Los schedules se guardan en
fn.config.json(la definicion persiste entre restarts). - El estado del scheduler se persiste por defecto en
<FN_FUNCTIONS_ROOT>/.fastfn/scheduler-state.json(para mantenerlast/next/status/errorentre restarts). - Fallos comunes (
last_status/last_error): 405: elmethoddel schedule no esta permitido por la policy de la funcion.413: elbodyexcediomax_body_bytes.429: la funcion estaba ocupada (gate de concurrencia).503: runtime caido/no saludable.- Retry/backoff (opcional):
- Setea
schedule.retry=truepara defaults, o un objeto: max_attempts(default3),base_delay_seconds(default1),max_delay_seconds(default30),jitter(default0.2).- El runtime clamp-ea:
max_attempts1..10, delays0..3600,jitter0..0.5. - Retries aplican a status
0,429,503y>=500. El scheduler actualizalast_errorconretrying .... - Consola:
GET /console/schedulermuestra schedules + keep_warm (requiereFN_UI_ENABLED=1). - Toggles globales:
FN_SCHEDULER_ENABLED=0deshabilita el scheduler.FN_SCHEDULER_INTERVALcontrola el tick (default1segundo, minimo efectivo1).FN_SCHEDULER_PERSIST_ENABLED=0deshabilita persistencia de estado.FN_SCHEDULER_PERSIST_INTERVALcontrola cada cuánto se escribe el estado (segundos, clamp5..3600).FN_SCHEDULER_STATE_PATHpermite override del path del archivo.
fn.env.json y secretos¶
fn.env.json: valores inyectados enevent.env- los secretos se marcan en el mismo archivo con
is_secret
Ejemplo:
{
"API_KEY": {"value": "secret-value", "is_secret": true},
"PUBLIC_FLAG": {"value": "on", "is_secret": false}
}
Diagrama de Flujo de Ejecución¶
flowchart LR
A["Request HTTP entrante"] --> B["Resolución de ruta"]
B --> C["Evaluación de política fn.config"]
C --> D["Adaptador de runtime"]
D --> E["Normalización de respuesta del handler"]
E --> F["Salida consistente con OpenAPI"]
Contrato¶
Define la forma esperada de request/response, campos de configuración y garantías de comportamiento.
Ejemplo End-to-End¶
Usa los ejemplos de esta página como plantillas canónicas para implementación y testing.
Casos Límite¶
- Fallbacks ante configuración faltante
- Conflictos de rutas y precedencia
- Matices por runtime