Jak to działa
Piszesz snippet w C#. To, co zwraca, w kilka sekund trafia pod publiczny adres HTTPS. Poniżej wszystko po kolei – w takiej kolejności, w jakiej na to trafisz.
Czym jest lambda
Lambda to snippet, który zwraca handler GenHTTP. Platforma go kompiluje, ładuje i montuje to, co zwrócił, pod twoim własnym adresem. Nie ma projektu, pliku builda ani instrukcji using. Wszystkie moduły GenHTTP są już zaimportowane.
return Content.From(Resource.FromString("hello"));
To kompletna lambda. Wdrożona pod /lambda/your-key/ odpowiada na każde żądanie słowem hello.
Snippet to instrukcje, a nie klasa. Na końcu zwraca coś, co potrafi obsługiwać żądania: handler albo builder, który go tworzy.
Twoja pierwsza lambda
- 1Kliknij Utwórz lambdę. Dostaniesz publiczny adres i klucz edytora. Klucz to jedyna droga powrotu, więc go zachowaj. Nikt go za ciebie nie odzyska.
- 2Trafiasz do centrum sterowania, a pierwsza wersja to już gotowa mała usługa REST. To tylko punkt wyjścia.
- 3Daj klucz edytora agentowi i powiedz, co ma zbudować – zapisuje nowe wersje przez MCP. Albo otwórz Kod i napisz wszystko samodzielnie: Sprawdź kompiluje bez zapisywania i pokazuje, co mówi kompilator – z nazwą pliku i numerem linii.
- 4Kliknij Wdróż. Teraz lambda jest online. Wcześniej nic nie jest dostępne, a każde kolejne wdrożenie wydłuża czas, przez który pozostaje online.
Centrum sterowania
Link do edytora otwiera centrum sterowania, a nie pole tekstowe: większość kodu piszą tu agenci, więc najpierw widzisz, jak radzi sobie twoja lambda. Na pasku bocznym jest sama lambda – czy jest online, jej adres i przycisk, gdy nowsza wersja czeka na wdrożenie – oraz jej sekcje. Rzadsze akcje, jak zmiana adresu czy usunięcie, są tam w menu ⋯.
- Przegląd
- Czym jest aplikacja, czy jest online, ile dziś było żądań i ile z nich się nie udało, ostatnia zmiana i ile zostało miejsca.
- Dokumentacja
- Czym jest aplikacja, dla kogo i po co, i dlaczego jest zbudowana właśnie tak – pisana przez agentów, zachowywana z każdą wersją.
- Zmień
- Napisz, co ma być inaczej, a agent na tym serwerze zrobi to na twoich oczach. Wypróbowuje zmianę w szkicu – kopii z własnym adresem – i udostępnia ją, gdy działa. Wyłącz Udostępnij po zakończeniu, jeśli chcesz najpierw samodzielnie wypróbować szkic. Zajmuje się tylko twoją aplikacją: prośbę, która jej nie dotyczy albo ma komuś zaszkodzić, odrzuca i mówi dlaczego.
- Szkice
- Zmiany wypróbowywane, zanim zostaną udostępnione, każda pod własnym adresem i na własnych danych testowych. Otwarty szkic ma własny kod, dane testowe i logi. Sekcja pojawia się, gdy istnieje szkic.
- Pliki
- Pliki danej wersji: jej kod i zasoby, czyli sam program. Kłódka albo globus pokazuje, czy są publicznie dostępne.
- Dane
- To, co lambda przechowuje w trakcie działania, wspólne dla wszystkich wersji: baza danych, obszar roboczy i sekrety, każde na własnej karcie. Przeglądaj tabele i pliki, przesyłaj pliki, ustawiaj sekrety albo włączaj i wyłączaj dany rodzaj. Widok uproszczony pokazuje tę sekcję, gdy tylko aplikacja coś przechowuje.
- Wersje
- Co zmieniła każda wersja, o co proszono i czym różni się od poprzedniej. Stąd wdrażasz wersję albo wracasz do starszej – albo tworzysz szkic na bazie dowolnej z nich.
- Wdrożenia
- Co i kiedy było online – i co to wyłączyło.
- Statystyki
- Żądania, błędy, czasy odpowiedzi i najczęściej odwiedzane ścieżki z ostatniej godziny albo ostatnich 24 godzin.
- Logi
- Żądania, to, co lambda wypisała, i stack trace każdego błędu – na bieżąco.
- Kod
- Tu piszesz kod ręcznie. Sprawdź kompiluje, Zapisz tworzy wersję, Wdróż wrzuca kod online. W szkicu Zapisz zostawia kod w szkicu i pokazuje go pod adresem szkicu.
Ctrl-Szapisuje;F12przechodzi do deklaracji. - Testy
- Jak aplikacja jest testowana automatycznie, razem ze skryptami i danymi testowymi. Tylko w widoku pełnym.
Każda sekcja działa tak samo: tytuł, ⓘ z wyjaśnieniem, akcje po prawej i – jeśli sekcja ma kilka widoków – rząd zakładek pod spodem. W sekcji Kod zakładki to pliki. Widok pełny zbiera sekcje w grupy: jak ludzie trafiają do lambdy, gdzie powstają zmiany, program i jego dane oraz jak lambda działa.
Ruch i log są trzymane w pamięci – do podglądu, nie do archiwizacji: po restarcie serwera zaczynają się od zera. Wersje i historia wdrożeń są zapisywane na stałe.
Opisz, dlaczego
Wersja to kod i opcjonalnie dwie notatki o nim: specyfikacja, czyli czego chce użytkownik i dlaczego – najlepiej jego słowami, oraz zmiana, czyli jedna linijka o tym, co robi ta wersja. Obie widać obok diffu w historii wersji, więc dlaczego zostaje obok co – dla ciebie i dla następnego agenta, który przeczyta historię, zanim cokolwiek zmieni.
POST /api/v1/lambdas/{editorKey}/versions { "files": [ { "name": "lambda.cs", "code": "..." } ], "specification": "Księga gości, którą ludzie mogą podpisać; wpisy muszą przetrwać restart", "change": "Trzyma wpisy w bazie danych, żeby przetrwały restart" }
Agenci przekazują te same dwa pola do write_code. W sekcji Kod o zmianę pyta okno zapisywania. Oba pola są opcjonalne. Za długi tekst nie jest odrzucany, tylko przycinany: specyfikacja do 4000 znaków, zmiana do 500. Szkic ma własne dwa pola, a wersja, w którą zostanie scalony, je przejmuje.
Dokumentacja i testy
Każda wersja przechowuje obok programu to, co o niej napisano: dokumentację – czym jest aplikacja, dla kogo i po co, i dlaczego jest zbudowana właśnie tak – oraz testy: jak automatycznie sprawdzić, że działa, razem ze skryptami i danymi testowymi. Agenci piszą je przy tworzeniu nowej lambdy i aktualizują przy każdej zmianie. Następny agent, który zmienia lambdę, najpierw je czyta, więc wie, do czego służy aplikacja i co musi działać dalej – czego sam kod nie mówi.
- .lambda/docs/product.md
- czym jest aplikacja, dla kogo, co ludzie z nią robią i po co
- .lambda/docs/decisions.md
- decyzje techniczne i dlaczego je podjęto
- .lambda/tests/README.md
- jak aplikacja jest testowana automatycznie i jak uruchomić testy
- .lambda/tests/…
- skrypty i dane testowe, których używają testy
To pliki wersji jak wszystkie inne, w folderze .lambda: historia pokazuje, co wersja w nich zmieniła, powrót do starszej wersji przywraca dokumentację, która była dla niej aktualna, a szkic ma własną kopię, która trafia online razem z nim. Nigdy nie są kompilowane ani serwowane i wliczają się do limitu zasobów wersji.
W centrum sterowania sekcja Dokumentacja pokazuje strony do przeczytania, a Testy – jak aplikacja jest testowana i pliki, które leżą obok; wersję wybiera się tak samo jak w plikach. Stronę można tam też edytować – zapisanie tworzy kolejną wersję. Widok prosty nazywa dokumentację O aplikacji i pokazuje tylko, do czego służy aplikacja – żeby to poprawić, powiedz agentowi.
Są pisane w języku, którego używasz w rozmowie z agentem, dla tego, kto jako następny zmieni aplikację – człowieka albo agenta. To nie kopia kodu, tylko to, do czego aplikacja służy i dlaczego.
Bezpieczne zmiany
Zapisana wersja nigdy się nie zmienia – i właśnie dlatego każdą warto zachować: każdą można porównać i przywrócić online dokładnie taką, jaka była. Żeby zmienić lambdę, z której ludzie korzystają, najpierw wypróbuj zmianę w szkicu.
- 1Zacznij od dowolnej wersji w sekcji Wersje albo poproś agenta, żeby utworzył szkic. To kopia kodu, zasobów, dokumentacji i testów tej wersji oraz danych lambdy.
- 2Zmieniaj go tyle razy, ile trzeba – w sekcji Kod albo prosząc agenta. Jego podgląd odpowiada pod osobnym adresem,
/features/…/, na własnych danych testowych. Odwiedzający lambdę nic z tego nie widzą, a nic, co zapisze szkic, nie trafia do danych lambdy. - 3Udostępnij szkic, gdy będzie gotowy: stanie się kolejną wersją razem ze swoimi notatkami i trafi online. Szkic znika razem z podglądem i danymi testowymi.
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 }
Nad kilkoma szkicami można pracować jednocześnie. Online może trafić tylko szkic aktualny względem najnowszej wersji, żeby nigdy nie cofnął wersji zapisanej po jego utworzeniu. Gdy wcześniej udostępniono inny, przenieś jego zmiany – albo poproś o to agenta – i oznacz szkic jako aktualny. Nic nie trafia online samo; tak ma być. API nazywa szkic „feature”, a udostępnienie go „merge”.
Więcej niż jeden plik
Typów nie trzeba dopisywać pod kodem, który ich używa. W sekcji Kod kliknij + obok plików, a nowy plik zostanie skompilowany obok snippetu, w tej samej przestrzeni nazw – nic nie trzeba importować. Nazwa bez rozszerzenia oznacza plik C#.
var shelf = new Shelf(); return Inline.Create() .Get(() => shelf.All()) .Post((Book book) => shelf.Add(book));
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);
Serwowanie strony
Stronę można serwować na dwa sposoby, a do tego jest jeszcze trzeci – na to, co ludzie przesyłają obok niej.
Jedna strona, wpisana w kod
Wystarczy do czegoś małego. Strona jest częścią snippetu.
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);
Folder z prawdziwymi plikami
Najlepszy wybór, gdy masz arkusz stylów i skrypt. Pliki dodajesz tak samo jak plik C#, a serwowane są dokładnie tak, jak je napiszesz. Nic ich nie kompiluje.
return Layout.Create() .Add("api", api) .Add(Assets.App("site"));
Przesłane pliki, z danych
Na to, co przesyłają ludzie albo tworzy lambda – zdjęcia, dokumenty – serwowane obok aplikacji. Nie na strony samej aplikacji: ich miejsce jest w folderze z plikami, gdzie trafiają do wersji razem z kodem, który ich potrzebuje.
return Layout.Create() .Add("api", api) .Add("uploads", Workspace.Files("uploads")) .Add(Assets.App("site"));
Frontend krok po kroku
Drugi sposób, krok po kroku. Każde demo serwuje swoją stronę właśnie tak, z folderu web – otwórz demo-crud, żeby zobaczyć przykład. Dema są tylko do odczytu; ich klucz edytora to ich nazwa.
- 1W sekcji Kod kliknij + obok plików i wpisz
site/index.html. Ukośnik w nazwie umieszcza plik w folderze, a rozszerzenie mówi, jakim plikiem jest. - 2Tak samo dodaj
site/app.cssisite/app.js. Strona odwołuje się do nich po nazwie, np.href="app.css", bo folder to katalog główny serwowanych plików, a nie część adresu. - 3Pliki, które nie są tekstem, np. obrazek czy font, dodasz tak: otwórz dowolny plik w
sitei kliknij przycisk przesyłania obok plików – plik trafi do tego samego folderu. PNG nie da się wpisać w edytor tekstu, więc to jedyna droga. - 4W
lambda.csserwuj ten folder:return Layout.Create().Add(Assets.App("site"));
- 5Kliknij Wdróż.
site/index.htmlodpowiada pod/,site/app.csspod/app.css, a na każdy adres, który nie pasuje do żadnego pliku, odpowiada strona. Dzięki temu frontend z własnym routingiem działa, nawet gdy ktoś odświeży głęboki link. - 6Dodaj obok API, a strona będzie miała z czym rozmawiać:
var api = Inline.Create().Get("notes", () => notes); return Layout.Create() .Add("api", api) .Add(Assets.App("site"));
Dwa miejsca na pliki
Lambda trzyma pliki w dwóch miejscach, a edytor pokazuje je osobno: Pliki to pliki wersji – program – a Dane to obszar roboczy – to, co program przechowuje. Cała różnica polega na tym, do kogo należą. Pliki wersji należą do tej wersji; dane należą do lambdy i wszystkie wersje je współdzielą.
| W wersji | W danych | |
|---|---|---|
| co zawiera | kod i zasoby: program, łącznie z frontendem – oraz jego dokumentacja i testy | wszystko, co zapisze lambda albo ktoś prześle |
| kiedy się zmienia | nigdy – zmiana to nowa wersja | w chwili, gdy coś zostanie zapisane |
| wdrożenie | wrzuca online dokładnie te pliki | nigdy ich nie rusza |
| powrót do starszej wersji | przywraca stare pliki | bez wpływu: wszystkie wersje je współdzielą |
| szkic | zaczyna jako ich kopia | działa na ich kopii |
| kiedy znika | razem ze starymi wersjami, po przekroczeniu limitu | razem z lambdą albo gdy je wyłączysz |
| dostęp z kodu przez | Assets | Workspace |
Nie mogą być jednym miejscem. Gdyby były, każde wdrożenie albo kasowałoby wszystko, co lambda zapisała od poprzedniego, albo z tego, co wdrażasz, nie dałoby się nigdy niczego usunąć. Gra z rankingiem potrzebuje tego drugiego, a strona, którą serwuje – pierwszego. Dlatego strona trafia do wersji, a ranking do danych.
Przechowywanie rekordów
Rekordy – wpisy, konta, zamówienia, głosy – należą do bazy danych: własnej bazy SQLite lambdy, którą włączasz w sekcji Dane. Kod otwiera połączenie przez Database.GetConnection() i czyta oraz zapisuje dane za pomocą Entity Framework Core, z własnym kontekstem, który mapuje tabele:
// migrations/V1__Create_notes.sql: // CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL); using (var connection = Database.GetConnection()) { new Evolve(connection) { Locations = [Assets.Root + "migrations"] }.Migrate(); } return Inline.Create() .Get("notes", () => { using var db = new Notes(Database.GetConnection()); return db.Entries.OrderBy(n => n.Id).Select(n => n.Text).ToList(); }) .Post("notes", (NoteInput input) => { using var db = new Notes(Database.GetConnection()); db.Entries.Add(new Note { Text = input.Text }); return db.SaveChanges(); }); record NoteInput(string Text); class Note { public long Id { get; set; } public string Text { get; set; } } // maps the table the migration made, on the connection it is handed class Notes(SqliteConnection connection) : DbContext { public DbSet<Note> Entries => Set<Note>(); protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite(connection, contextOwnsConnection: true); protected override void OnModelCreating(ModelBuilder model) => model.Entity<Note>().ToTable("notes"); }
Jej tabele tworzą migracje: pliki SQL dostarczane z wersją w migrations/, które Evolve stosuje po kolei przy starcie lambdy – każdą tylko raz, więc nowa wersja uruchamia zawsze tylko to, co nowe. Nigdy nie zmieniaj migracji, która została już zastosowana; zmiana tabeli to kolejny plik.
Jak wszystkie dane, baza danych jest wspólna dla wszystkich wersji, wdrożenia i powroty do starszej wersji jej nie ruszają, a szkic pracuje na jej kopii. W sekcji Dane widzisz jej tabele i to, co w nich jest – widok uproszczony nazywa to wpisami. Pobierz jako projekt .NET dołącza ją jako zwykły plik SQLite.
Twórz kontekst tam, gdzie go potrzebujesz, i zwalniaj go po użyciu, a korzystaj z niego synchronicznie – ToList i SaveChanges, a nie ToListAsync i SaveChangesAsync. Tabele tworzą migracje, nigdy Entity Framework. Demo demo-crud robi to wszystko.
Przechowywanie plików
Workspace to prywatny katalog, w którym lambda może czytać i zapisywać: miejsce na pliki – zdjęcia, które ktoś przesyła, dokument, który lambda tworzy, model, który wczytuje. Rekordy należą do bazy danych, a to, co wiadomo o pliku – kto go przesłał i kiedy – też jest rekordem.
// uploads go into a folder of their own, served as they are Workspace.CreateFolder("photos"); return Layout.Create() .Add("photos", Workspace.Files("photos")) .Add("upload", Inline.Create().Post(async (Stream body) => { using var content = new MemoryStream(); await body.CopyToAsync(content); Workspace.WriteBytes($"photos/{Guid.NewGuid():N}.jpg", content.ToArray()); }));
Są też ReadBytes, WriteBytes, Delete, List, CreateFolder oraz Tree/Files/App do serwowania. Nic więcej w systemie plików nie jest dostępne.
Klucze i hasła
Klucz API, hasło czy token należy do sekretów, a nie do kodu – tam miałaby go każda wersja, każde pobranie i każdy, kto czyta historię. Kod odczytuje sekret po nazwie:
var weather = new System.Net.Http.HttpClient(); weather.DefaultRequestHeaders.Add("X-Api-Key", Secret.Read("WEATHER_API_KEY")); return Inline.Create() .Get("today", async () => await weather.GetStringAsync("https://weather.example/today"));
Włącz sekrety w sekcji Dane i ustaw tam wartość. Po zapisaniu nigdy nie jest już pokazywana – ani tobie, ani agentowi; możesz ją tylko zastąpić. Lista pokazuje, które nazwy kod odczytuje, choć nie mają jeszcze wartości, a przegląd o nie prosi. Secret.Exists mówi, czy sekret jest ustawiony – dla kodu, który działa i bez niego. Jak wszystkie dane, sekrety są wspólne dla wszystkich wersji, a szkic pracuje na kopii.
Są przechowywane w postaci zaszyfrowanej, kluczem, którego nie ma w bazie danych. W pobranym projekcie Secret.Read("NAME") odczytuje zmienną środowiskową NAME – same wartości zostają tutaj.
Websockety
Obsługiwane – i to nie na doczepkę. Demo demo-game łączy graczy w pary i prowadzi każdą grę na serwerze. Najprostsza wersja to trzy callbacki:
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);
Gdy strona tylko nasłuchuje – licznik, kanał wiadomości, tablica wyników – prostsze są zdarzenia wysyłane przez serwer (server-sent events): jedna długa odpowiedź, do której serwer stale dopisuje, a przeglądarka łączy się ponownie sama. Demo demo-live wysyła w ten sposób każdy głos wszystkim, którzy je oglądają. Tak czy inaczej to serwer przesyła to, co się zmieniło. Strona, która co kilka sekund pyta od nowa, wysyła żądanie za każdym razem, niezależnie od tego, czy coś się zmieniło, i i tak jest spóźniona.
Jedna rzecz zaskakuje każdego: przeglądarka nie może ustawić nagłówków przy nawiązywaniu połączenia websocket. Przekaż to, czego potrzebuje handler, w query stringu, skąd odczyta to przez connection.Request.Header.Query, albo wyślij sekrety w pierwszej wiadomości.
Czego nie da się zrobić
Twój kod działa na wspólnym serwerze, więc część C# jest odrzucana jeszcze przed kompilacją: uruchamianie procesów, otwieranie własnych socketów, ładowanie assembly, sięganie do systemu plików poza obszarem roboczym i refleksja użyta, żeby to wszystko obejść. Tak samo czekanie na zadanie przez .Result lub .Wait() zamiast await: żądania działają na jednym wątku na rdzeń, a zadanie musiałoby się zakończyć na tym samym wątku, który na nie czeka.
Cała reszta jest dostępna, łącznie z pełnym API modułów GenHTTP. Jeśli coś zostanie odrzucone, dowiesz się, w której linii i dlaczego – a nie tylko, że się nie udało.
Zabierz kod ze sobą
Pobierz jako projekt .NET w menu edytora daje ci całość: solution, które możesz otworzyć, uruchomić przez dotnet run i zachować na zawsze. Potrzebuje tylko pakietu GenHTTP, a w zestawie jest Dockerfile, żeby zbudować i uruchomić je jako kontener.
Twój snippet staje się plikiem Project.cs, a Program.cs serwuje to, co snippet zwraca. Pozostałe pliki trafiają do projektu bez zmian. Workspace i Assets stają się dwoma folderami obok programu, z tymi samymi metodami, osobno w folderze Platform, więc w kodzie nie trzeba nic zmieniać. Secret odczytuje tam zmienne środowiskowe o tej samej nazwie; wartości zostają tutaj. Dokumentacja i testy trafiają do folderów docs i tests. Database otwiera database/database.db – pobrany projekt zawiera ten plik razem z rekordami, które zapisała twoja aplikacja.
Warto to wiedzieć, zanim cokolwiek tu zbudujesz: to, co piszesz, należy do ciebie i możesz to zabrać w całości. To, że kod działa na tej maszynie, w niczym go do niej nie przywiązuje.
Publikowanie kodu
Jeśli to, co zbudujesz, może pomóc komuś innemu, opublikuj kod: otwórz sekcję Open source w centrum sterowania, wybierz licencję – MIT, chyba że wolisz inną – i włącz publikację. Kod dostanie własną stronę wśród aplikacji open source, gdzie każdy może go przeczytać, dać mu gwiazdkę i pobrać dowolną wersję jako ten sam projekt, który daje Pobierz jako projekt .NET, razem z licencją.
Publikowana jest każda wersja, także wcześniejsze, razem z dokumentacją, testami i zmianą, którą wprowadziła. To, co aplikacja przechowuje – jej rekordy, zapisane pliki, wartości kluczy i haseł – nigdy nie jest publikowane, podobnie jak twoje prośby, sformułowane twoimi słowami, i to, kto korzysta z aplikacji. Po wyłączeniu strona znika; gwiazdki zostają zachowane na wypadek ponownej publikacji.
Wszystko, co jest w kodzie, staje się publiczne, łącznie z wcześniejszymi wersjami. Miejsce klucza czy hasła jest wśród kluczy i haseł w sekcji Dane, nigdy w kodzie – opublikowanym czy nie.
Niech zrobi to agent
Pod /mcp działa endpoint MCP. Podłącz do niego agenta, a zrobi wszystko to, co edytor: przeczyta dokumentację, przeczyta całe demo, zapisze pliki, skompiluje je i wdroży. Pod spodem to samo API.
Przy okazji mówi, dlaczego coś robi – write_code przyjmuje specyfikację i zmianę – i może sprawdzić, co wdrożył: read_logs zwraca ostatnie żądania lambdy, to, co wypisała, i stack trace każdego wyjątku. Tak agent dowiaduje się, że jego kod działa, zamiast to zakładać. Ty widzisz to samo w centrum sterowania. W trakcie pracy pisze dokumentację i testy, czyta je, zanim cokolwiek zmieni, i uruchamia testy pod adresem szkicu, zanim wrzuci szkic online. Strona, którą ludzie mają znaleźć, dostaje tytuł, opis, ikonę i podgląd, który widać, gdy ktoś udostępnia link do niej. Na dole stron, które buduje, dodaje małą linijkę z informacją, że powstały w GenHTTP Lambda – powiedz mu, jeśli wolisz jej nie mieć, a ją usunie.