GenHTTP Lambda

Zo werkt het

Je schrijft een stukje C#. Wat dat teruggeeft, staat binnen een paar seconden online op een openbaar adres, via HTTPS. Hier staat alles, in de volgorde waarin je het tegenkomt.

Wat een lambda is

Een lambda is een snippet die een GenHTTP-handler teruggeeft. Het platform compileert hem, laadt hem en hangt wat hij teruggeeft onder je eigen adres. Er is geen project, geen buildbestand en geen using nodig. Alle GenHTTP-modules zijn al voor je geïmporteerd.

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

Dat is een complete lambda. Gedeployd op /lambda/your-key/ beantwoordt hij elk request met het woord hello.

De snippet bestaat uit statements, niet uit een class. Het laatste wat hij doet, is iets teruggeven dat requests kan afhandelen: een handler, of een builder daarvoor.

Je eerste lambda

  1. 1
    Klik op Lambda aanmaken. Je krijgt een openbaar adres en een editorsleutel. De sleutel is de enige weg terug, dus bewaar hem goed. Niemand kan hem voor je herstellen.
  2. 2
    Je komt terecht in het dashboard, waar als eerste versie al een kleine REST-service klaarstaat. Dat is maar een beginpunt.
  3. 3
    Geef de editorsleutel aan een agent en vertel wat hij moet bouwen. Hij schrijft nieuwe versies via MCP. Of open Code en schrijf het zelf: Controleren compileert zonder iets op te slaan en laat zien wat de compiler ervan vindt, met bestand en regel.
  4. 4
    Klik op Deployen. Nu staat hij online. Tot dan is er niets bereikbaar. Opnieuw deployen verlengt hoe lang hij online blijft.

Het dashboard

De editorlink opent geen tekstvak maar een dashboard. De meeste code hier schrijven agents, dus het eerste wat je ziet, is hoe het met je lambda gaat. In de zijbalk staat de lambda zelf: of hij online is, zijn adres, en een knop als er een nieuwere versie klaarstaat om online te gaan. Daaronder staan de onderdelen. Wat je zelden doet, zoals het adres wijzigen of de lambda verwijderen, zit daar achter het menu ⋯.

Overzicht
Of hij online is, hoeveel requests hij vandaag had en hoeveel daarvan misgingen, de laatste wijziging, en hoeveel ruimte er nog over is.
Aanpassen
Zeg wat er anders moet, en de agent op deze server doet het terwijl jij meekijkt. Hij werkt in een concept, probeert het daar uit en voegt het samen tot de volgende versie zodra het werkt. Zet Online zetten als het klaar is uit als je het concept eerst zelf wilt uitproberen.
Concepten
Wijzigingen waaraan naast de lambda wordt gewerkt: elk concept probeer je uit op een eigen adres, en het wordt samengevoegd tot de volgende versie zodra het goed is. Open je een concept, dan heeft het zijn eigen code, data en logs.
Bestanden
De bestanden van een versie: de code en assets, het programma zelf. Een slotje of een wereldbol laat zien of ze openbaar bereikbaar zijn.
Data
Wat de lambda bewaart terwijl hij draait, gedeeld door elke versie: de workspace. Kijk erin, upload en verwijder bestanden, of zet hem uit.
Versies
Wat elke versie veranderde en wat er gevraagd werd, en het verschil met de vorige. Van hieruit deploy je of zet je een versie terug, en vanuit elke versie kun je een concept starten.
Deployments
Wat wanneer online stond, en waardoor het offline ging.
Statistieken
Requests, fouten, responstijden en de meest opgevraagde paden, over het afgelopen uur of de afgelopen dag.
Logs
Requests, output en de stacktrace van alles wat misging, live.
Code
Zelf schrijven. Controleren compileert, Opslaan maakt een versie, Deployen zet hem online. In een concept bewaart Opslaan de code in het concept, en zet Voorvertoning deployen hem online op het adres van het concept. Ctrl-S slaat op; F12 springt naar een declaratie.

Elk onderdeel werkt hetzelfde: een titel, een ⓘ met uitleg, acties rechts en, als er meer dan één weergave is, een rij tabs eronder. Bij de code zijn de tabs de bestanden.

Het verkeer en de logs staan in het geheugen. Ze zijn om mee te kijken, niet om te bewaren: na een herstart van de server beginnen ze opnieuw. Versies en de deploygeschiedenis worden wel opgeslagen.

Uitleggen waarom

Een versie is de code, plus eventueel twee notities: de specificatie (wat de gebruiker wil en waarom, zo veel mogelijk in eigen woorden) en de wijziging (één regel over wat de versie doet). Ze staan naast de diff in de versiegeschiedenis. Zo blijft het waarom bewaard naast het wat, voor jou en voor de volgende agent die de geschiedenis leest voordat hij iets verandert.

POST /api/v1/lambdas/{editorKey}/versions
{
  "files": [ { "name": "lambda.cs", "code": "..." } ],
  "specification": "Een gastenboek dat mensen kunnen tekenen; berichten moeten een herstart overleven",
  "change": "Bewaart berichten in de workspace, zodat ze een herstart overleven"
}

Agents geven dezelfde twee velden mee aan write_code. In Code wordt bij het opslaan om de wijziging gevraagd. Beide zijn optioneel. Een lange specificatie wordt afgekapt op 4000 tekens en een wijziging op 500, in plaats van geweigerd. Een concept heeft zijn eigen twee, en de versie waarin het wordt samengevoegd, neemt ze over.

Veilig aanpassen

Een versie verandert nooit meer als hij eenmaal is opgeslagen, en juist daardoor is elke versie het bewaren waard: je kunt ze allemaal vergelijken en precies zoals ze waren weer online zetten. Wil je een lambda aanpassen die mensen gebruiken, start dan in plaats daarvan een concept.

  1. 1
    Start het onder Concepten, of vanuit een willekeurige versie. Het is een kopie van de code en assets van die versie, en van de data van de lambda.
  2. 2
    Pas het zo vaak aan als nodig, in Code of door het aan de agent te vragen. Voorvertoning deployen zet het online op een eigen adres, /features/…/, met een eigen kopie van de data. Bezoekers van de lambda zien er niets van, en niets wat het wegschrijft, komt in de data van de lambda terecht.
  3. 3
    Klik op Samenvoegen zodra het goed is: het wordt de volgende versie, met zijn notities, en gaat meteen online als je dat wilt. Het concept verdwijnt dan, met zijn voorvertoning en zijn kopie van de data.
POST /api/v1/lambdas/{editorKey}/features
{ "name": "Ranglijst" }

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

Je kunt aan meerdere concepten tegelijk werken. Alleen een concept dat op de nieuwste versie is gebaseerd, kan worden samengevoegd. Zo maakt samenvoegen nooit een versie ongedaan die is opgeslagen nadat het concept begon. Is er eerst een ander samengevoegd, haal dan de wijzigingen daarvan binnen (of vraag de agent dat te doen) en baseer het concept daarna op de nieuwste versie. Niets wordt vanzelf samengevoegd; dat is met opzet.

Meer dan één bestand

Types hoeven niet onder de code te staan die ze gebruikt. Klik in Code op + naast de bestanden. Het nieuwe bestand wordt naast de snippet gecompileerd, in dezelfde namespace, dus je hoeft niets te importeren om erbij te kunnen. Een naam zonder extensie wordt als C# gezien.

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);

Een pagina serveren

Er zijn twee manieren om een pagina te serveren, en nog een voor wat mensen ernaast uploaden.

Eén pagina, inline geschreven

Prima voor iets kleins. De pagina zit in de snippet zelf.

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);

Een map met echte bestanden

De juiste keuze voor alles met een stylesheet en een script. Je voegt de bestanden toe zoals een C#-bestand, en ze worden precies zo geserveerd als je ze schreef. Er wordt niets gecompileerd.

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

Geüploade bestanden, uit de data

Voor wat mensen uploaden of wat de lambda aanmaakt, zoals foto's en documenten, geserveerd naast de app. Niet voor de pagina's van de app zelf: die horen in een map met bestanden, zodat ze in dezelfde versie zitten als de code die ze nodig heeft.

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

Een frontend, stap voor stap

De tweede manier, helemaal uitgewerkt. Elke demo serveert zijn pagina zo, vanuit een map die web heet. Open demo-crud om er een te bekijken. Demo's zijn alleen-lezen; hun editorsleutel is hun naam.

  1. 1
    Klik in Code op + naast de bestanden en typ site/index.html. Een slash in de naam zet het bestand in een map; de extensie bepaalt wat voor bestand het is.
  2. 2
    Voeg site/app.css en site/app.js op dezelfde manier toe. Je pagina verwijst ernaar met alleen de naam, zoals href="app.css". De map is namelijk de root van wat er geserveerd wordt, geen deel van het adres.
  3. 3
    Voor alles wat geen tekst is, zoals een afbeelding of een font, open je een bestand in site en klik je op de uploadknop naast de bestanden: het komt in dezelfde map terecht. Een PNG kun je niet in een teksteditor typen, dus zo krijg je hem erin.
  4. 4
    Serveer de map in lambda.cs:
    return Layout.Create().Add(Assets.App("site"));
  5. 5
    Klik op Deployen. site/index.html antwoordt op /, site/app.css op /app.css, en elk adres dat bij geen enkel bestand past, krijgt de pagina als antwoord. Zo blijft een frontend met eigen routing werken als iemand een deeplink herlaadt.
  6. 6
    Zet er een API naast, dan heeft de pagina iets om mee te praten:
    var api = Inline.Create().Get("notes", () => notes);
    
    return Layout.Create()
                 .Add("api", api)
                 .Add(Assets.App("site"));

De twee plekken voor bestanden

Een lambda bewaart bestanden op twee plekken, en de editor toont ze apart: Bestanden bevat de bestanden van een versie (het programma), en Data bevat de workspace (wat het programma bewaart). Het verschil zit in van wie ze zijn. De bestanden van een versie horen bij die versie; de data hoort bij de lambda, en elke versie deelt die.

In een versieIn de data
wat erin staatde code en assets: het programma, frontend inbegrepenalles wat de lambda wegschrijft of iemand uploadt
wanneer het verandertnooit: een wijziging is een nieuwe versiezodra er iets naar wordt geschreven
een deployzet precies deze bestanden onlineraakt het nooit aan
terugzettenhaalt de oude bestanden teruggeen effect: elke versie deelt het
een conceptbegint als kopie ervanwerkt met een kopie ervan
wanneer het verdwijntmet oude versies, boven de limietmet de lambda, of als je het uitzet
in code te bereiken alsAssetsWorkspace

Het kan niet één en dezelfde plek zijn. Dan zou een deploy alles wissen wat je lambda sindsdien had weggeschreven, of zou er nooit iets weg kunnen uit wat hij meelevert. Een spel met een ranglijst wil het tweede; de pagina die het serveert wil het eerste. Dus de pagina gaat in de versie, en de ranglijst in de data.

Data bewaren

Workspace is een privémap waarin je lambda mag lezen en schrijven. Hier hoort alles wat langer moet bestaan dan een request, of een deployment.

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;
             });

Er zijn ook ReadBytes, WriteBytes, Delete, List, CreateFolder, en Tree/Files/App om hem te serveren. Verder is niets op het bestandssysteem bereikbaar.

Websockets

Ondersteund, en niet als bijzaak. De demo demo-game koppelt spelers aan elkaar en laat elk spel op de server draaien. De simpelste vorm is drie 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);

Waar iedereen in trapt: een browser kan geen headers meesturen bij een websocket-handshake. Geef wat de handler nodig heeft mee in de query, waar hij het leest uit connection.Request.Header.Query, of stuur geheimen als eerste bericht.

Wat niet mag

Je code draait op een gedeelde server, dus een deel van C# wordt al vóór het compileren geweigerd: processen starten, eigen sockets openen, assemblies laden, het bestandssysteem buiten je workspace benaderen, en reflection om daar omheen te komen.

Al het andere is er, inclusief de hele module-API van GenHTTP. Wordt er iets geweigerd, dan hoor je welke regel en waarom, niet alleen dat het misging.

Alles meenemen

Met Downloaden als .NET-project in de editor krijg je alles mee: een solution die je kunt openen, kunt draaien met dotnet run en mag houden. Er zit één package reference in en geen spoor van dit platform.

Je snippet wordt de body van Program.cs, in een host die serveert wat hij teruggeeft. Je andere bestanden komen precies mee zoals je ze schreef. Workspace en Assets worden twee mappen naast de code, met dezelfde methodes, dus er hoeft niets in je code te veranderen.

Goed om te weten voordat je hier iets bouwt: wat je schrijft is van jou, en je neemt het in zijn geheel mee. Dat je code hier draait, betekent niet dat hij hier vastzit.

Het aan een agent overlaten

Er is een MCP-endpoint op /mcp. Koppel er een agent aan en hij kan alles wat de editor kan: de handleiding lezen, een demo helemaal lezen, bestanden schrijven, ze compileren en deployen. Eronder zit dezelfde API.

Onderweg legt hij uit waarom: write_code krijgt de specificatie en de wijziging mee. En hij kan bekijken wat hij heeft gedeployd: read_logs geeft de recente requests van de lambda, de output en de stacktrace van elke exception. Zo controleert een agent of zijn code werkt, in plaats van het aan te nemen. Jij ziet hetzelfde in het dashboard.

Meer daarover →