>_ Tlycode
home Landing menu_book Docs Hub
cloud Hosting Docs smart_toy Agent Docs
Základní
dashboard Přehled account_tree Architektura route Routing
Vývoj
web React integrace check_circle Validace pattern Vzory cloud_upload Deploy
Reference
api Runtime API warning TSTL Gotchas

TlyCode Framework

Dokumentace serverless webového frameworku postaveného na platformě Tlycode. TypeScript → Lua → Rust s React UI.

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ší.

info Info

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

Browser → HTTP Request → Lua Runtime → Route Handler
  → 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

# Compilation 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

  1. cd react-app && npm run build — Vite buildne React app jako IIFE s externím React/ReactDOM
  2. Embed script přečte výstup a zapíše do src/react-bundle-content.ts
  3. npm run build (npx tstl) — zkompiluje veškerý TypeScript do dist/bundle.lua

Struktura projektu

Server-side (src/)

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

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ÚčelPovinný
index.tsBarrel export (pouze handlery)Ano
[module].handlers.tsHTTP request handleryAno
[module].repository.tsDatabázové dotazyKdyž potřeba DB
[module].validation.tsValidační třídy formulářůKdyž potřeba formuláře
[module].types.tsDoménové typyKdyž potřeba typy
[module].const.tsKonstantyKdyž potřeba konstanty

Entry Point (src/main.ts)

Lua hosting runtime volá tři exportované funkce z main.ts:

  • config() — vrací konfiguraci aplikace
  • init() — volána jednou při startu
  • main(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 WebRouterPaths = "/";
export type ApiRouterPaths = never;

Přidání nové routy

  1. Přidejte cestu do union type v router.types.ts
  2. Vytvořte handler funkci v příslušném modulu .handlers.ts
  3. Zaregistrujte routu v router.ts

Signatura handleru

export function renderHello(request: Request, response: Response): Response {
    response.content = getReactPageTemplate('Hello World', "HelloWorld", {});
    return response;
}

Request / Response

Lua hosting runtime volá tři exportované funkce z main.ts:

  • config() — vrací konfiguraci
  • init() — volána jednou při startu
  • main(request: Request): Response — HTTP vstupní bod

web React integrace

Jak to funguje

  1. Route handler volá getReactPageTemplate(title, componentName, props)
  2. Vygenerované HTML načte React 18 z CDN (unpkg) a embedded JS bundle
  3. window.__REACT_RENDER__(componentName, props, containerId) namountuje komponentu
  4. React obdrží veškerá data jako props — žádné client-side data fetching
  5. Navigace je server-side — žádný client-side routing

Klíčové soubory

SouborÚčel
src/react.tsgetReactPageTemplate(), renderReactComponent()
src/react-build.tsCesty k assetům (bundle URL, CSS URL)
src/react-bundle-content.tsEmbedded React bundle (auto-generovaný)
react-app/src/main.tsxEntry point, window.__REACT_RENDER__
react-app/src/registry.tsMapa názvů → React komponent

Registr komponent

export const registry: Record<string, ComponentType<any>> = {
    HelloWorld: HelloWorldPage,
};

Přidání nové React komponenty

  1. Vytvořte stránku v react-app/src/pages/
  2. Přidejte do registru v react-app/src/registry.ts
  3. Použijte v handleru: getReactPageTemplate('Title', "MyPage", {{ ...props }})
  4. 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

if (request.method === "post") {
  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

export class ProductForm {
  @Transform((v: string) => v?.trim())
  @Required()
  @MinLength(2)
  name: string = '';

  @Required()
  price: string = '0';
}

Dostupné dekorátory

DecoratorPopis
@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

export function renderHello(request: Request, response: Response): Response {
  response.content = getReactPageTemplate('Hello World', "HelloWorld", {});
  return response;
}

Handler s daty

export function renderItems(request: Request, response: Response): Response {
  const items = findAllItems();
  response.content = getReactPageTemplate('Items', "ItemList", {
    items: items.map(i => ({ id: String(i.id), name: i.name })),
  });
  return response;
}

Repository funkce

export function findAllItems(): DbItem[] {
  return sqlQuery<DbItem>(
    `SELECT *, price::float as price, created_at::text as created_at FROM items ORDER BY id DESC`,
    []
  );
}

Cachovaný data fetch

function getItems(): Item[] {
  const cached = appCacheGet("items");
  if (cached) return jsonDecode(cached);
  const items = findAllItems();
  appCacheSet("items", jsonEncode(items), 60000);
  return items;
}

Session (login / protected route)

// Login
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:

$ cd react-app && npm run build && cd ..
$ npm run build
warning Pozor

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.sh              # Standard deploy (Lua bundle + git metadata)
$ ./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 config = getConfig();
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

sqlQuery<T>(query: string, params: any[]): T[]
warning Pozor

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

httpGet(url: string, headers?: Record<string, string>): HttpResponse
httpPost(url: string, body: string, headers?: Record<string, string>): HttpResponse
httpRequest(method: string, url: string, body?: string, headers?: Record<string, string>): HttpResponse

Cache

appCacheGet(key: string): string | null
appCacheSet(key: string, value: string, ttlMs: number): void
appCacheRemove(key: string): void

JSON

jsonEncode(value: any): string
jsonDecode<T>(json: string): T

Crypto

hashPassword(password: string): string
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

jwtSign(payload: any, secret: string, expiresIn?: number): string
jwtVerify<T>(token: string, secret: string): T | null
jwtDecode<T>(token: string): T | null

Date / Time

now(): number           // Unix timestamp (seconds)
nowMillis(): number     // Unix timestamp (milliseconds)
dateFormat / dateParse / dateAdd / dateDiff
dateToISO / dateFromISO

Cloud Storage

storageUpload / storageUploadBytes / storageDownload / storageDelete
storageGetUrl / storageGetSignedUrl / storageList / storageExists

String / URL / Regex / Math

// String
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.

// BAD — "" is truthy in Lua, never calls generateSlug()
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.

// GOOD — always cast DECIMAL and TIMESTAMP
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.

// BAD — nil truncates Lua array
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 MethodPouž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ů.

lightbulb SQL SELECT Template

Vždy přetypujte DECIMAL a TIMESTAMP sloupce v každém SELECT dotazu:

sqlQuery<T>(`SELECT *,
  price::float as price,
  created_at::text as created_at,
  updated_at::text as updated_at
FROM table_name WHERE ...`
, [params]);