dashboard Přehled
TlyCode Framework je serverless webový framework postavený na platformě Tlycode. Kompiluje TypeScript do Lua pomocí TSTL. UI rendering zajišťují React komponenty vložené do serverem generovaných HTML stránek.
code TypeScript → Lua
Kód se píše v TypeScriptu a kompiluje do Lua přes TSTL pro běh na Lua JIT runtime.
web React UI
React 18 komponenty pro rendering UI. Server generuje HTML s embedded bundlem.
cloud Serverless
Běží na Google Cloud Run. Automatické škálování, žádná správa serverů.
database PostgreSQL
Neon serverless PostgreSQL s globálně dostupnou funkcí sqlQuery().
cloud_upload Cloud Storage
Google Cloud Storage pro soubory, obrázky a statické assety.
api Runtime API
60+ globálních runtime funkcí pro DB, HTTP, cache, crypto, JWT, email a další.
Většinu operací platformy (deploy, DB dotazy, logy, storage) lze provádět přes MCP connector přímo z AI agenta.
account_tree Architektura
Hybridní React model
Server (TSTL/Lua) zajišťuje routing a data. Veškeré UI renderování provádějí React komponenty. Server serializuje data jako JSON props a generuje HTML stránku, která načte React z CDN a embedded app bundle.
Průběh požadavku
→ Handler calls getReactPageTemplate(title, componentName, props)
→ Returns full HTML page with:
- React 18 from CDN (unpkg)
- App JS bundle (embedded IIFE)
- <script>window.__REACT_RENDER__(componentName, props)</script>
→ Browser loads React, mounts component with props
Klíčové vlastnosti
- Žádný client-side routing — každá navigace je plný HTTP požadavek
- Žádné client-side data fetching — veškerá data přijdou jako serializované props
- Formuláře přes HTTP POST — standardní odesílání formulářů, žádné SPA chování
- React pouze pro rendering — business logika zůstává na serveru
Kompilační pipeline
src/ → TSTL compiler → dist/bundle.lua → Lua JIT runtime (Cloud Run)
react-app/src/ → Vite build → dist/index-[hash].js
dist/index-[hash].js → embed script → src/react-bundle-content.ts → included in TSTL build
Kroky buildu
cd react-app && npm run build— Vite buildne React app jako IIFE s externím React/ReactDOM- Embed script přečte výstup a zapíše do src/react-bundle-content.ts
npm run build(npx tstl) — zkompiluje veškerý TypeScript do dist/bundle.lua
Struktura projektu
Server-side (src/)
├── main.ts # Entry point — config(), init(), main()
├── global.d.ts # Runtime API declarations (@noSelf)
├── global.types.ts # Global types (Request, Response, Config)
├── config.ts # App configuration
├── validator.ts # Decorator-based validation
├── template.ts # HTML template wrapper
├── react.ts # getReactPageTemplate(), renderReactComponent()
├── react-build.ts # Asset paths config
├── react-bundle-content.ts # Auto-generated embedded React bundle
└── modules/
├── router.ts # Route definitions
├── router.types.ts # Route path union types
└── app/
└── hello/ # Domain module
Client-side (react-app/src/)
├── main.tsx # Entry point (window.__REACT_RENDER__)
├── registry.ts # Component name → React component map
└── pages/
└── public/
└── HelloWorldPage.tsx
Struktura doménového modulu
| Soubor | Účel | Povinný |
|---|---|---|
index.ts | Barrel export (pouze handlery) | Ano |
[module].handlers.ts | HTTP request handlery | Ano |
[module].repository.ts | Databázové dotazy | Když potřeba DB |
[module].validation.ts | Validační třídy formulářů | Když potřeba formuláře |
[module].types.ts | Doménové typy | Když potřeba typy |
[module].const.ts | Konstanty | Když potřeba konstanty |
Entry Point (src/main.ts)
Lua hosting runtime volá tři exportované funkce z main.ts:
config()— vrací konfiguraci aplikaceinit()— volána jednou při startumain(request)— HTTP request handler
route Routing
Routy se definují v src/modules/router.ts jako pole objektů s path, route a type.
Definice rout
Routy jsou definovány jako pole objektů { path, route, type }. Type může být 'render' pro stránky nebo 'api' pro API endpointy.
Type-safe cesty
export type ApiRouterPaths = never;
Přidání nové routy
- Přidejte cestu do union type v
router.types.ts - Vytvořte handler funkci v příslušném modulu
.handlers.ts - Zaregistrujte routu v router.ts
Signatura handleru
response.content = getReactPageTemplate('Hello World', "HelloWorld", {});
return response;
}
Request / Response
Lua hosting runtime volá tři exportované funkce z main.ts:
config()— vrací konfiguraciinit()— volána jednou při startumain(request: Request): Response— HTTP vstupní bod
web React integrace
Jak to funguje
- Route handler volá
getReactPageTemplate(title, componentName, props) - Vygenerované HTML načte React 18 z CDN (unpkg) a embedded JS bundle
window.__REACT_RENDER__(componentName, props, containerId)namountuje komponentu- React obdrží veškerá data jako props — žádné client-side data fetching
- Navigace je server-side — žádný client-side routing
Klíčové soubory
| Soubor | Účel |
|---|---|
src/react.ts | getReactPageTemplate(), renderReactComponent() |
src/react-build.ts | Cesty k assetům (bundle URL, CSS URL) |
src/react-bundle-content.ts | Embedded React bundle (auto-generovaný) |
react-app/src/main.tsx | Entry point, window.__REACT_RENDER__ |
react-app/src/registry.ts | Mapa názvů → React komponent |
Registr komponent
HelloWorld: HelloWorldPage,
};
Přidání nové React komponenty
- Vytvořte stránku v
react-app/src/pages/ - Přidejte do registru v
react-app/src/registry.ts - Použijte v handleru:
getReactPageTemplate('Title', "MyPage", {{ ...props }}) - Rebuildněte:
cd react-app && npm run build→ embed script →npm run build
Vite Build
- Format: IIFE — načítá se přes <script> tag, ne ESM
- External:
react,react-dom— načítají se z CDN za běhu - CSS: jeden soubor (cssCodeSplit: false)
check_circle Validace
Dekorátor-based validace pomocí experimentálních TypeScript dekorátorů v src/validator.ts. Validace běží na server-side (TSTL/Lua) při zpracování POST dat z formulářů.
Použití v handleru
const raw = getPayloudData<Record<string, string>>(request);
try {
const data = transformValidate(ProductForm, raw);
insertProduct(data.name, data.slug, Number(data.price));
response.status = 302;
response.headers["Location"] = "/admin/products";
return response;
} catch (error) {
if (error instanceof ValidationError) { /* re-render form */ }
}
}
Definice validačních tříd
@Transform((v: string) => v?.trim())
@Required()
@MinLength(2)
name: string = '';
@Required()
price: string = '0';
}
Dostupné dekorátory
| Decorator | Popis |
|---|---|
@Required() | Pole nesmí být prázdné |
@MinLength(n) | Minimální délka řetězce |
@MaxLength(n) | Maximální délka řetězce |
@Range(min, max) | Číselný rozsah |
@Custom(fn) | Vlastní validační funkce |
@Transform(fn) | Transformace hodnoty před validací |
@Type(typeFn) | Validace vnořeného objektu |
pattern Vzory
Jednoduchý page handler
response.content = getReactPageTemplate('Hello World', "HelloWorld", {});
return response;
}
Handler s daty
const items = findAllItems();
response.content = getReactPageTemplate('Items', "ItemList", {
items: items.map(i => ({ id: String(i.id), name: i.name })),
});
return response;
}
Repository funkce
return sqlQuery<DbItem>(
`SELECT *, price::float as price, created_at::text as created_at FROM items ORDER BY id DESC`,
[]
);
}
Cachovaný data fetch
const cached = appCacheGet("items");
if (cached) return jsonDecode(cached);
const items = findAllItems();
appCacheSet("items", jsonEncode(items), 60000);
return items;
}
Session (login / protected route)
response = setSession({ user: { id: user.id, token: uniqueKey() } }, response);
// Protected route
return withSessionRefresh<UserSession>(request, response, (req, res) => {
const session = getSession<UserSession>(req);
if (!session?.user) { /* redirect to /login */ }
});
cloud_upload Deploy
Deploy probíhá přes deploy script, který kompiluje TypeScript do Lua a nahraje bundle na hosting platformu.
React build před deplojem
Pokud se změnily React komponenty, je nutné před deplojem rebuildnout React bundle:
$ npm run build
Deploy script spouští npm run build (TSTL) automaticky, ale NErebuildne React. Vždy rebuildněte React ručně, pokud se změnily .tsx soubory.
Deploy příkazy
$ ./scripts/deploy-local.sh # Local deploy (no commit gate, /api/deploy/local)
$ ./scripts/deploy.sh --react # Deploy React dist to Cloud Storage
$ SKIP_BUILD=1 ./scripts/deploy.sh # Skip build step
Deploy React assetů
Deploy React dist složky do Cloud Storage. Endpoint vrátí base_url — veřejnou URL, kde jsou assety přístupné.
Rozlišení URL assetů
Base URL pro assety se určuje z konfigurace aplikace. Přednostně se používá CDN_URL, jako fallback STORAGE_URL.
const baseUrl = config.CDN_URL || config.STORAGE_URL;
const assetUrl = `${baseUrl}/images/logo.png`;
api Runtime API
Všechny funkce jsou globálně dostupné (deklarovány v src/global.d.ts s anotací @noSelf). Není nutný žádný import.
Database
DECIMAL sloupce: vždy přetypujte price::float as price. TIMESTAMP sloupce: created_at::text as created_at. NULL parametry: použijte NULLIF($N, 0) pattern.
HTTP Client
httpPost(url: string, body: string, headers?: Record<string, string>): HttpResponse
httpRequest(method: string, url: string, body?: string, headers?: Record<string, string>): HttpResponse
Cache
appCacheSet(key: string, value: string, ttlMs: number): void
appCacheRemove(key: string): void
JSON
jsonDecode<T>(json: string): T
Crypto
verifyPassword(password: string, hash: string): boolean
sha256(data: string): string | md5(data: string): string
hmacSha256(data: string, key: string): string
base64Encode / base64Decode / base64UrlEncode / base64UrlDecode
randomBytes(length: number): string
JWT
jwtVerify<T>(token: string, secret: string): T | null
jwtDecode<T>(token: string): T | null
Date / Time
nowMillis(): number // Unix timestamp (milliseconds)
dateFormat / dateParse / dateAdd / dateDiff
dateToISO / dateFromISO
Cloud Storage
storageGetUrl / storageGetSignedUrl / storageList / storageExists
String / URL / Regex / Math
trim / toLower / toUpper / slugify / stringSplit / stringContains / stringReplace
// URL
urlEncode / urlDecode / parseUrl / parseUrlQuery / buildUrlQuery
// Regex
regexTest / regexMatch / regexMatchAll / regexReplace
// Math
round / ceil / floor / abs / mathMin / mathMax / clamp
formatNumber / formatCurrency
Další funkce
- Logging:
logInfo/logWarn/logError/logDebug - File I/O:
fileRead/fileWrite/fileDelete/dirList - Email:
sendEmail(to, subject, body, options?) - Images:
imageResize/imageThumbnail/imageInfo - PDF:
generatePdf(html, options?) - Redis: kompletní podpora strings, hashes, lists, sets, sorted sets, pub/sub
- Config:
getConfig(key?)/uniqueKey()
warning TSTL / Lua Gotchas
Kritické rozdíly mezi TypeScriptem a kompilovaným Lua runtime. Tato pravidla je nutné dodržovat, aby se předešlo těžko odladitelným chybám.
1. Prázdný string je truthy v Lua
V JavaScriptu je "" falsy, ale v Lua je "" truthy. Vzor s || fallbackem tiše selže.
const slug = data.slug || generateSlug(data.name);
// GOOD — explicit check
const slug = (data.slug !== '' && data.slug !== undefined) ? data.slug : generateSlug(data.name);
2. DECIMAL/NUMERIC sloupce vrací null
Lua SQL driver nedokáže číst PostgreSQL DECIMAL/NUMERIC sloupce — vrací null. Vždy přetypujte: price::float as price. Stejně tak TIMESTAMP: created_at::text as created_at.
sqlQuery(`SELECT *, price::float as price, created_at::text as created_at FROM products`, []);
3. Null/nil v polích ořezává parametry
Lua nil v polích je ořezává. To způsobuje nesoulad počtu SQL bind parametrů. Použijte NULLIF pattern.
sqlQuery("INSERT INTO products (name, category_id) VALUES ($1, $2)", [name, categoryId]);
// GOOD — NULLIF pattern
sqlQuery("INSERT INTO products (name, category_id) VALUES ($1, NULLIF($2, 0))",
[name, categoryId !== null ? categoryId : 0]);
4. charAt() pracuje s bajty, ne UTF-8
String.charAt() operuje nad raw bajty. Vícebajtové UTF-8 znaky (české háčky/čárky = 2 bajty) se rozlomí. Používejte operace na úrovni stringu jako stringReplace().
5. Nepodporované JS string metody
| JS Method | Použijte místo toho |
|---|---|
string.includes() | stringContains(text, search) |
string.startsWith() | stringStartsWith(text, prefix) |
string.endsWith() | stringEndsWith(text, suffix) |
string.split() | stringSplit(text, delimiter) |
string.replace() | stringReplace(text, search, repl) |
string.trim() | trim(text) |
string.toLowerCase() | toLower(text) |
6. dateParse formátové tokeny
Používejte tokeny YYYY-MM-DD HH:mm:ss (NE Go-style). Pro spolehlivost preferujte ruční parsování podřetězců.
Vždy přetypujte DECIMAL a TIMESTAMP sloupce v každém SELECT dotazu:
price::float as price,
created_at::text as created_at,
updated_at::text as updated_at
FROM table_name WHERE ...`, [params]);