GenHTTP Lambda

Cómo funciona

Escribes un fragmento de C#. Lo que devuelva queda alojado en una dirección pública, con HTTPS, en unos segundos. Aquí tienes todo, en el orden en que te lo vas a encontrar.

Qué es una lambda

Una lambda es un fragmento de código que devuelve un handler de GenHTTP. La plataforma lo compila, lo carga y monta lo que devuelve en tu propia dirección. No hay proyecto, ni archivo de build, ni sentencias using. Todos los módulos de GenHTTP ya vienen importados.

return Content.From(Resource.FromString("hello"));

Eso ya es una lambda completa. Desplegada en /lambda/your-key/, responde a cada petición con la palabra «hello».

El fragmento son sentencias, no una clase. Lo último que hace es devolver algo que pueda atender peticiones: un handler o un builder que cree uno.

Tu primera lambda

  1. 1
    Haz clic en Crear mi lambda. Recibes una dirección pública y una clave de edición. La clave es la única forma de volver a entrar, así que guárdala. Nadie puede recuperarla por ti.
  2. 2
    Llegas a su centro de control, con un pequeño servicio REST ya escrito como primera versión. Es solo un punto de partida.
  3. 3
    Pásale la clave de edición a un agente y dile qué construir: escribe versiones nuevas a través de MCP. O abre Código y escríbelo tú: Comprobar compila sin guardar nada y te dice qué opina el compilador, con archivo y línea.
  4. 4
    Haz clic en Desplegar. Ya está en línea. Antes de eso no se puede acceder a nada, y cada vez que vuelves a desplegar alargas el tiempo que sigue en línea.

El centro de control

El enlace de edición abre un centro de control, no un cuadro de texto: aquí casi todo el código lo escriben agentes, así que lo primero que ves es cómo está tu lambda. La barra lateral muestra la lambda (si está en línea, su dirección y un botón cuando hay una versión más nueva esperando para ponerse en línea) y sus secciones. Lo que se hace pocas veces, como cambiar la dirección o eliminarla, está en el menú ⋯ de esa barra.

Resumen
Si está en línea, cuántas peticiones tuvo hoy y cuántas fallaron, el último cambio y cuánto espacio le queda.
Cambiar
Di qué debería ser distinto y el agente de este servidor lo hace mientras miras. Trabaja en un borrador, lo prueba ahí y lo fusiona en la siguiente versión cuando funciona. Desactiva Ponerlo en línea al terminar para probar tú el borrador antes.
Borradores
Cambios en los que se trabaja al lado de la lambda: cada uno se prueba en su propia dirección y se fusiona en la siguiente versión cuando está bien. Al abrirlo, un borrador tiene su propio código, sus datos y sus logs.
Archivos
Los archivos de una versión: su código y sus recursos, el programa en sí. Un candado o un globo indica si el público puede acceder a ellos.
Datos
Lo que la lambda guarda mientras se ejecuta, compartido por todas las versiones: el workspace. Puedes ver lo que contiene, subir y eliminar archivos, o desactivarlo.
Versiones
Qué cambió cada versión, qué se pidió y la diferencia con la anterior. Desde aquí despliegas o vuelves atrás, o empiezas un borrador a partir de cualquiera de ellas.
Despliegues
Qué estuvo en línea y cuándo, y qué lo desconectó.
Estadísticas
Peticiones, fallos, tiempos de respuesta y las rutas más pedidas, en la última hora o el último día.
Logs
Sus peticiones, lo que imprimió y el stack trace de cualquier error, en tiempo real.
Código
Para escribirlo a mano. Comprobar compila, Guardar crea una versión y Desplegar la pone en línea. En un borrador, Guardar lo guarda en el borrador y Desplegar la vista previa lo pone en línea en la dirección del borrador. Ctrl-S guarda; F12 va a una declaración.

Todas las secciones funcionan igual: su título, un ⓘ que la explica, sus acciones a la derecha y, si tiene más de una vista, una fila de pestañas debajo. En el código, las pestañas son sus archivos.

El tráfico y los logs se guardan en memoria, para mirarlos, no para conservarlos: si el servidor se reinicia, empiezan de cero. Las versiones y el historial de despliegues sí se guardan.

Explicar el porqué

Una versión es el código y, si quieres, dos notas sobre él: la especificación, qué quiere el usuario y por qué, con sus palabras si se puede, y el cambio, una línea sobre lo que hace la versión. Se muestran junto al diff en el historial de versiones, así que el porqué se queda junto al qué: para ti y para el próximo agente que lea el historial antes de tocar nada.

POST /api/v1/lambdas/{editorKey}/versions
{
  "files": [ { "name": "lambda.cs", "code": "..." } ],
  "specification": "Un libro de visitas que la gente pueda firmar; las entradas tienen que sobrevivir a un reinicio",
  "change": "Guarda las entradas en el workspace para que sobrevivan a un reinicio"
}

Los agentes pasan esos mismos dos campos a write_code. En Código, al guardar se te pide el cambio. Los dos son opcionales; una especificación larga se recorta a 4000 caracteres y un cambio a 500, en vez de rechazarse. Un borrador tiene sus propios dos campos, y la versión en la que se fusiona se queda con ellos.

Cambiarla sin riesgo

Una versión nunca cambia una vez guardada, y eso es lo que hace que valga la pena conservarlas todas: cualquiera de ellas se puede comparar y volver a poner en línea exactamente como estaba. Para cambiar una lambda que la gente usa, empieza un borrador.

  1. 1
    Empiézalo en Borradores o desde cualquier versión. Es una copia del código y los recursos de esa versión, y de los datos de la lambda.
  2. 2
    Cámbialo tantas veces como haga falta, en Código o pidiéndoselo al agente. Desplegar la vista previa lo pone en línea en su propia dirección, /features/…/, con su propia copia de los datos. Los visitantes de la lambda no ven nada de esto, y nada de lo que escribe llega a los datos de la lambda.
  3. 3
    Cuando esté bien, Fusionar lo convierte en la siguiente versión, con sus notas, y lo pone en línea enseguida si quieres. El borrador desaparece con ello, junto con su vista previa y su copia de los datos.
POST /api/v1/lambdas/{editorKey}/features
{ "name": "Ranking" }

PUT  /api/v1/lambdas/{editorKey}/features/{feature}/files?deploy=true
POST /api/v1/lambdas/{editorKey}/features/{feature}/merge
{ "deploy": true }

Se puede trabajar en varios borradores a la vez. Solo se puede fusionar uno basado en la versión más nueva, para que una fusión nunca deshaga una versión guardada después de que empezara el borrador. Si antes se fusionó otro, lleva sus cambios a este (o pídeselo al agente) y después basa el borrador en la versión más nueva. Nada se fusiona solo; es a propósito.

Más de un archivo

Los tipos no tienen por qué estar debajo del código que los usa. En Código, haz clic en + junto a los archivos: el archivo nuevo se compila junto al fragmento, en el mismo namespace, así que no hay que importar nada para usarlo. Un nombre sin extensión se toma como C#.

lambda.cs
var shelf = new Shelf();

return Inline.Create()
             .Get(() => shelf.All())
             .Post((Book book) => shelf.Add(book));
Shelf.cs
public sealed class Shelf
{
    private readonly List<Book> _books = [];

    public IEnumerable<Book> All() => _books;

    public Book Add(Book book)
    {
        _books.Add(book);
        return book;
    }
}

public record Book(string Title, string Author);

Servir una página

Hay dos formas de servir una página, y una más para lo que la gente sube junto a ella.

Una página escrita en el código

Sirve para algo pequeño. La página forma parte del fragmento.

var page = Resource.FromString("""
                              <!doctype html>
                              <title>Mine</title>
                              <h1>It works</h1>
                              """)
                   .Type(new ContentType("text/html; charset=utf-8"));

return Content.From(page);

Una carpeta de archivos reales

Lo que necesitas para cualquier cosa con hoja de estilos y script. Los archivos se añaden igual que un archivo C# y se sirven tal como están escritos. Nada los compila.

return Layout.Create()
             .Add("api", api)
             .Add(Assets.App("site"));

Archivos subidos, desde los datos

Para lo que la gente sube o la lambda crea (fotos, documentos), servido junto a la app. No para las páginas de la propia app: esas van en una carpeta de archivos, donde se versionan junto con el código que las necesita.

return Layout.Create()
             .Add("api", api)
             .Add("uploads", Workspace.Files("uploads"))
             .Add(Assets.App("site"));

Un frontend, paso a paso

La segunda forma, completa. Todas las demos sirven su página así, desde una carpeta llamada web: abre demo-crud para ver una. Las demos son de solo lectura; su clave de edición es su nombre.

  1. 1
    En Código, haz clic en + junto a los archivos y escribe site/index.html. Un nombre con barra pone el archivo en una carpeta; un nombre con extensión se toma como el tipo de archivo que indica.
  2. 2
    Añade site/app.css y site/app.js de la misma forma. Tu página los enlaza por su nombre, como en href="app.css", porque la carpeta es la raíz de lo que se sirve, no parte de la dirección.
  3. 3
    Para todo lo que no sea texto, como una imagen o una fuente, abre un archivo de site y usa el botón de subir que está junto a los archivos: va a parar a la misma carpeta. Un PNG no se puede escribir en un editor de texto, así que esa es la forma de meterlo.
  4. 4
    En lambda.cs, sirve la carpeta:
    return Layout.Create().Add(Assets.App("site"));
  5. 5
    Haz clic en Desplegar. site/index.html responde en /, site/app.css en /app.css, y cualquier dirección que no coincida con un archivo se responde con la página. Así, un frontend con su propio enrutamiento sigue funcionando cuando alguien recarga en un enlace profundo.
  6. 6
    Añade una API al lado y la página tendrá con quién hablar:
    var api = Inline.Create().Get("notes", () => notes);
    
    return Layout.Create()
                 .Add("api", api)
                 .Add(Assets.App("site"));

Los dos lugares donde viven los archivos

Una lambda guarda archivos en dos lugares, y el editor los muestra por separado: Archivos contiene los archivos de una versión (el programa) y Datos contiene el workspace (lo que el programa guarda). La diferencia está en de quién son. Los archivos de una versión pertenecen a esa versión; los datos pertenecen a la lambda, y todas las versiones los comparten.

En una versiónEn los datos
qué contieneel código y los recursos: el programa, frontend incluidolo que escribe la lambda o sube alguien
cuándo cambianunca: un cambio es una versión nuevaen cuanto se escribe algo en ellos
un desplieguepone en línea exactamente estos archivosnunca los toca
volver atrástrae de vuelta los archivos anterioresno les afecta: todas las versiones los comparten
un borradorempieza como una copia de ellostrabaja con una copia de ellos
cuándo desaparececon las versiones antiguas, al pasar el límitecon la lambda, o cuando desactivas el workspace
cómo se accede desde el códigoAssetsWorkspace

No pueden estar en un solo lugar. Si lo estuvieran, un despliegue borraría todo lo que tu lambda haya escrito desde entonces, o nunca se podría quitar nada de lo que publica. Un juego con ranking quiere lo segundo; la página que sirve, lo primero. Por eso la página va en la versión y el ranking, en los datos.

Guardar datos

Workspace es un directorio privado que tu lambda puede leer y escribir. Es el lugar para todo lo que tenga que sobrevivir a una petición o a un despliegue.

var notes = new List<string>();

if (Workspace.Exists("notes.json"))
{
    notes.AddRange(JsonSerializer.Deserialize<List<string>>(Workspace.ReadText("notes.json")) ?? []);
}

return Inline.Create()
             .Get("notes", () => notes)
             .Post("notes", (string text) =>
             {
                 notes.Add(text);
                 Workspace.WriteText("notes.json", JsonSerializer.Serialize(notes));
                 return notes.Count;
             });

También tienes ReadBytes, WriteBytes, Delete, List, CreateFolder y Tree/Files/App para servirlo. No se puede acceder a nada más del sistema de archivos.

WebSockets

Funcionan, y no son un añadido de última hora. La demo demo-game empareja jugadores y ejecuta cada partida en el servidor. La forma más simple son tres callbacks:

var room = new ConcurrentDictionary<IReactiveConnection, string>();

var socket = Websocket.Functional()
                      .OnOpen(c => { room[c] = "someone"; return ValueTask.CompletedTask; })
                      .OnMessage(async (c, text) =>
                      {
                          foreach (var other in room.Keys)
                          {
                              await other.WritePayloadAsync(text);
                          }
                      })
                      .OnClose((c, _) => { room.TryRemove(c, out _); return ValueTask.CompletedTask; });

return Layout.Create().Add("chat", socket);

Hay un detalle que sorprende a todo el mundo: un navegador no puede poner cabeceras en el handshake de un websocket. Pasa lo que necesite el handler en la query, donde lo lee desde connection.Request.Header.Query, o envía los secretos en el primer mensaje.

Lo que no te deja hacer

Tu código se ejecuta en un servidor compartido, así que parte de C# se rechaza antes de compilar: iniciar procesos, abrir tus propios sockets, cargar ensamblados, acceder al sistema de archivos fuera de tu workspace y usar reflexión para saltarte cualquiera de esas reglas.

Todo lo demás está disponible, incluida toda la API de módulos de GenHTTP. Si algo se rechaza, te decimos en qué línea y por qué, no solo que falló.

Llévate tu código

En el editor, Descargar como proyecto .NET te da todo listo para llevártelo: una solución que puedes abrir, ejecutar con dotnet run y conservar. Tiene una sola referencia a un paquete y ningún rastro de esta plataforma.

Tu fragmento pasa a ser el cuerpo de Program.cs, dentro de un host que sirve lo que devuelve. Tus otros archivos llegan exactamente como los escribiste. Workspace y Assets se convierten en dos carpetas junto al código, con los mismos métodos, así que no tienes que cambiar nada de tu código.

Conviene saberlo antes de crear nada aquí: lo que escribes es tuyo y te lo llevas entero. Ejecutarlo en esta máquina no te ata a esta máquina.

Que lo haga un agente

Hay un endpoint MCP en /mcp. Conecta un agente y podrá hacer todo lo que hace el editor: leer la guía, leer una demo completa, escribir archivos, compilarlos y desplegar. Por debajo es la misma API.

El agente explica el porqué sobre la marcha (write_code recibe la especificación y el cambio) y puede revisar lo que desplegó: read_logs devuelve las peticiones recientes de la lambda, lo que imprimió y el stack trace de cualquier excepción. Así un agente comprueba que su código funciona en vez de suponerlo. Tú ves lo mismo en el centro de control.

Más sobre esto →