Klient a jeho metody
createPatroClient vrátí objekt s dvaceti metodami (změřeno nad
packages/render/src/client.ts). Tahle stránka je jejich úplný seznam, rozdělený
podle toho, k čemu jsou — ne podle abecedy, protože k sobě patří jinak.
Než se do toho pustíte, dvě věci platné pro všechny:
- Metoda buď vrátí hodnotu, nebo
null(legitimní „to tu není“ / „beze změny“), nebo odmítne příslib (chyba). Rozvaha je v kapitole o napojení. - Kde je parametr
localevolitelný, server bez něj dosadí výchozí jazyk webu.
Web jako celek
Sekce “Web jako celek”getTenant()
Sekce “getTenant()”Konfigurace webu — to, co potřebujete znát dřív, než se zeptáte na cokoli jiného: jazyky, výchozí jazyk, časové pásmo, úseky adres pro autory a hledání a identitu webu (název, popis, hotové adresy loga a ikony).
const tenant = await client.getTenant()// {// id, slug, name,// description: string | null,// logoUrl: string | null, iconUrl: string | null,// locales: ["cs", "en"], defaultLocale: "cs",// timezone: "Europe/Prague",// bylineRouteSlug: "autor", searchRouteSlug: "hledani",// hiddenBlocks: […],// }logoUrl a iconUrl jsou hotové adresy, ne identifikátory — z ID by je
šablona složit neuměla. null znamená, že web tu věc nemá; nevymýšlejte za něj
náhradu, prohlížeč si o ikonu řekne sám a dostane 404, což je normální stav webu
bez ikony.
getVocabulary()
Sekce “getVocabulary()”Slovník adres: jména taxonomií a slugy typů obsahu. Potřebujete ho dřív, než
víte, na co se díváte — /kategorie/aktuality a /sluzby/uklid mají identický
tvar a rozhoduje mezi nimi jedině to, jestli je kategorie taxonomie tohohle
webu.
const vocabulary = await client.getVocabulary()// {// taxonomies: [{ name: "kategorie", label: "Kategorie" }, …],// types: [{ slug: "clanek", name: "Článek" }, …],// }Taxonomie se adresuje strojovým jménem, typ obsahu slugem — je to
rozdíl, který se plete a v adrese se projeví. Popisek (label / name) je to,
co patří do nadpisu výpisu.
Obsah
Sekce “Obsah”listEntries(filter?)
Sekce “listEntries(filter?)”Odlehčený seznam publikovaných stránek — bez těla, jen to, co potřebuje výpis. Bez zúžení je to celý index ve výchozím jazyce, první dávka. S filtrem je to výpis termu, typu, archivu nebo autora — jednou cestou, aby se ty stránky nemohly rozejít v tom, co je vlastně vidět.
const entries = await client.listEntries({ locale: "cs", term: "aktuality", // slug termu taxonomy: "kategorie", // zúží `term` na jednu taxonomii type: "clanek", // slug typu obsahu year: 2026, month: 8, // archiv (měsíc bez roku se ignoruje) byline: "jan-novak", // stránky, pod kterými je podepsaný page: 2, // stránkuje se od 1})Celkový počet nechodí — server ho vědomě neposílá, byl by to agregační dotaz navíc. „Je toho víc“ se pozná z plné dávky, viz stránkování.
getEntryBySlug(slug, locale?)
Sekce “getEntryBySlug(slug, locale?)”Jedna publikovaná stránka i s tělem a rozřešeným typem (popisky a druhy polí).
const entry = await client.getEntryBySlug("o-nas", "cs")if (entry === null) { Astro.response.status = 404}null je jediná odpověď pro koncept, cizí slug i slug publikovaný jen
v jiném jazyce. Server je vědomě neodlišuje — rozdíl by prozrazoval, co
v administraci leží. Nedělejte tedy tichý fallback na jiný jazyk; tady vede
poctivá 404.
getFrontSlug(locale?)
Sekce “getFrontSlug(locale?)”Slug titulní stránky v daném jazyce, nebo null, když titulka nastavená není
a kořen má ukázat výpis.
const front = await client.getFrontSlug(locale)Vrací jen slug, ne obsah — a je to schválně. Kořen s ním udělá vnitřní
přesměrování (Astro.rewrite) na detail, takže se titulka nemůže vykreslovat
jinak než kterákoli jiná stránka.
getPreview(token)
Sekce “getPreview(token)”Náhled nepublikované verze proti podepsanému tokenu. Ten přijde v adrese
(?preview=…) a nese v sobě položku, verzi i web; klient ho jen přepošle.
const previewToken = Astro.url.searchParams.get("preview")const preview = previewToken === null ? null : await client.getPreview(previewToken)// { entry, status: "draft" | "scheduled" | "published", matchesLive, scheduledAt }Vrací stránku ve stejném tvaru jako getEntryBySlug, aby se náhled kreslil
touž cestou a nemohl vypadat jinak než výsledek publikace. Navíc nese stav, ze
kterého se skládá text výstražného pruhu.
Dvě věci k tomu patří a nejsou samozřejmé:
- Náhledovou odpověď nesmíte nechat uložit — je to nepublikovaný obsah na
veřejné adrese. Nastavte
cache-control: no-store, private. nullje odpověď na jakoukoli vadu (chybějící, prošlý i pozměněný token, cizí web, smazaná položka) → táž 404 jako u neexistujícího slugu.
searchEntries({ q, locale?, limit?, offset? })
Sekce “searchEntries({ q, locale?, limit?, offset? })”Fulltext v publikovaném obsahu. Hledá se nad živou verzí — koncepty ani naplánované stránky v indexu nejsou vůbec.
const page = await client.searchEntries({ q: dotaz, locale, limit: LISTING_PAGE_SIZE, offset: pageOffset(cislo),})// { total: number, items: [...] }Na rozdíl od výpisů tady total je, takže se dá spočítat poslední stránka.
Prázdný dotaz se na server vůbec neposílá (vrátí prázdnou stránku) — je to
odpověď na otázku, kterou nemá smysl klást.
⚠️ snippet NIKDY nevkládejte jako HTML
Sekce “⚠️ snippet NIKDY nevkládejte jako HTML”Každý výsledek nese snippet — úryvek okolo shody. Zvýraznění v něm je
značkami <mark>/</mark>, které kolem shody dopsala SQLite. Vypadá to
jako připravené HTML a je to past: okolní text je syrový obsah stránky, tedy
to, co napsal redaktor. set:html={hit.snippet} z toho udělá uloženou XSS.
Správně se úryvek rozseká podle těch dvou značek a text jde do stránky jako text — zvýraznění vznikne skutečným prvkem, ne vloženým řetězcem:
---const parts = (snippet: string) => snippet .split(/<mark>|<\/mark>/) .map((text, index) => ({ text, hit: index % 2 === 1 })) .filter((part) => part.text.length > 0)---
<p> {parts(hit.snippet).map((part) => part.hit ? <mark>{part.text}</mark> : <>{part.text}</>, )}</p><mark> a </mark> jsou jediné, co do úryvku přidává SQLite. Cokoli dalšího, co
v něm vypadá jako značka, je obyčejný text od redaktora — a zůstane textem.
Navigace
Sekce “Navigace”listMenus(locale?)
Sekce “listMenus(locale?)”Menu, která web v daném jazyce má — jen jména a popisky, bez stromů.
const menus = await client.listMenus(locale)// [{ name: "hlavni", label: "Hlavní menu", locale: "cs", itemCount: 7 }, …]Je to obrana proti tiché díře: bez téhle metody by šablona musela hádat
dohodnutý název menu a při neshodě by prostě nevykreslila nic. Prázdné pole =
web menu nemá; není to chyba a není to null.
itemCount tam není pro ozdobu. Když si šablona vybírá menu sama, musí umět
přeskočit prázdné — jinak navigace zmizí z celého webu jen proto, že prázdné
menu má abecedně dřívější popisek.
getMenu(name, locale?)
Sekce “getMenu(name, locale?)”Jedno menu jako rozřešený strom: každá položka má hotovou adresu a šablona si k ní nic nedopočítává. Položky, jejichž cíl na webu k vidění není, ze stromu vypadly už na serveru i s podstromem.
const menu = await client.getMenu("hlavni", locale)// {// name, label, locale,// items: [{ id, label, href, target, titleAttr, cssClasses, children: [...] }, …],// }null = menu toho jména v tom jazyce neexistuje. Není to chyba: web, který menu
ještě nemá založené, se má vykreslit bez navigace, ne spadnout.
getRedirects(knownVersion?)
Sekce “getRedirects(knownVersion?)”Mapa pravidel přesměrování — přesné shody a vzory. Pravidla drží patro, vykonává je vaše šablona.
const map = await client.getRedirects(drzenaVerze)if (map === null) { // beze změny — nech si, co máš}Ručně to skoro nikdo volat nebude, protože celý mechanismus (paměť, vyhodnocení,
hlášení) je hotový v createRedirectMiddleware — viz
kapitola o routování.
reportRedirects(report)
Sekce “reportRedirects(report)”Pošle nastřádaná hlášení zpátky do patra: nenalezené adresy a zásahy pravidel jednou zprávou.
await client.reportRedirects({ notFound: [...], hits: [...] })Dávkově schválně — volá se z pozadí (waitUntil), ne v cestě odpovědi
návštěvníkovi. Když selže, je to na vás: klient odmítne příslib a jestli se kvůli
neodeslanému hlášení nemá kazit stránka, rozhodujete vy (hotový middleware
rozhoduje, že ne).
Taxonomie a archiv
Sekce “Taxonomie a archiv”getTerms(taxonomy)
Sekce “getTerms(taxonomy)”Termy jedné taxonomie i s počty viditelných položek.
const terms = await client.getTerms("kategorie")// [{ id, slug: "aktuality", label: "Aktuality", parentId: null, count: 12 }, …]Seznam je plochý i u stromové taxonomie — zanoření se pozná z parentId
a strom si z toho poskládá šablona, když ho chce.
Stránka výpisu termu z nich bere popisek do nadpisu — a hlavně z nich pozná, že
term toho slugu vůbec existuje. null = taxonomie toho jména tu není (404);
prázdné pole je něco jiného: taxonomie existuje a nemá termy.
listArchivePeriods(params?)
Sekce “listArchivePeriods(params?)”Období, ve kterých web něco publikoval — roky, s months: true i jednotlivé
měsíce.
const periods = await client.listArchivePeriods({ type: "clanek", locale, months: true,})// [{ year: 2026, month: 8, count: 4 }, …]month: null je shrnutí celého roku, ne chybějící hodnota — bez months
přijdou jen tyhle roční řádky. Prázdné pole = web v tom jazyce (a typu) nic
publikovaného nemá; není to chyba.
Autoři
Sekce “Autoři”listBylines(locale?)
Sekce “listBylines(locale?)”Autoři webu v daném jazyce, seřazení podle jména, a k nim definice jejich vlastních polí.
const { bylines, fields } = await client.listBylines(locale)Definice chodí i tady, ne jen u detailu, a je to nutnost: fieldValues je holá
mapa klíč → hodnota a bez popisků a druhů by ji nebylo čím vykreslit. Profil,
který v tom jazyce neexistuje, v seznamu není.
getByline(slug, locale?)
Sekce “getByline(slug, locale?)”Jeden profil i s definicemi polí — týž tvar jako type.fields vedle data
u stránky, takže to zvládne tatáž komponenta.
const profile = await client.getByline("jan-novak", locale)if (profile === null) { Astro.response.status = 404}null = autor toho slugu v tom jazyce tu není. Udělejte z toho 404, ne prázdný
profil.
Tělo stránky, oblasti a diskuse
Sekce “Tělo stránky, oblasti a diskuse”Tahle pětice patří k blokové půlce. Metody jsou tu popsané kvůli úplnosti; zapojení má vlastní kapitolu.
resolveListingData(requests, locale?)
Sekce “resolveListingData(requests, locale?)”Dopočet výpisových bloků — na seznam požadavků vrátí seznam hotových výpisů (odkazy, strom menu, adresa hledání). Redaktor si do těla stránky vloží blok „výpis článků“ a tohle je to, čím se naplní.
const data = await client.resolveListingData(pozadavky, locale)Odpověď se nesmí držet v paměti. Je to jediná část obsahu, která je čerstvá právě proto, že se na ni ptáme pokaždé: publikace nové stránky by otiskem verze oblastí nehnula, takže by výpis zamrzl. Prázdný seznam požadavků se na server neposílá vůbec, takže stránka bez výpisového bloku neplatí ani kolo navíc.
listWidgetAreas(locale?, knownVersion?)
Sekce “listWidgetAreas(locale?, knownVersion?)”Oblasti widgetů, které má web v daném jazyce vykreslit — jméno, popisek, popis a obsah, všechno jednou odpovědí.
const areas = await client.listWidgetAreas(locale, drzenaVerze)if (areas === null) { // beze změny}getWidgetArea(name, locale?)
Sekce “getWidgetArea(name, locale?)”Jedna oblast — pro web, který si celý seznam nedrží.
const paticka = await client.getWidgetArea("paticka", locale)null = oblast toho jména tu není, nebo ji web přestal kreslit (osiřela).
Ty dva případy server vědomě neodlišuje a pro toho, kdo se ptá, znamenají totéž:
nevykreslovat.
declareWidgetAreas(declaration)
Sekce “declareWidgetAreas(declaration)”Ohlásí patru, které oblasti šablona kreslí — celý seznam, ne přírůstek, takže co v něm není, patro označí za osiřelé.
const result = await client.declareWidgetAreas({ areas: [{ name: "paticka", label: "Patička", description: "…" }],})// čísla o tom, co se PRÁVĚ ZMĚNILO — „nic nového" je řada nulTohle je ten mechanismus, kvůli kterému administrace nabídne redaktorovi
k vyplnění přesně ty oblasti, které šablona opravdu kreslí, a žádné jiné. Ručně
ho volat nemusíte — defineWidgetAreas z @patro/render ho zavolá sám, a jen
při rozchodu.
getComments(entryId, options?)
Sekce “getComments(entryId, options?)”Schválené vlákno pod položkou — celý strom o jedné úrovni naráz.
const thread = await client.getComments(entry.id, { sort: "best", // Wilsonova mez; jinak pořadí vzniku knownVersion: drzena,})Plochá (nevláknová) podoba čtení tudy vědomě nevede.
Podmíněné dotazy (knownVersion)
Sekce “Podmíněné dotazy (knownVersion)”Tři metody berou otisk podoby, kterou si držíte — getRedirects,
listWidgetAreas, getComments. Pošle se jako If-None-Match, a když se od té
verze nic nezměnilo, server odpoví 304 a klient vrátí null = nech si, co
máš.
Je to ten častý případ, ne okrajový: patička se kreslí na každé stránce, takže se web ptá pořád dokola, ale mění se jen při uložení obsahu.
A je to důvod, proč null u nich neznamená chybu ani prázdno. Prázdná mapa
pravidel je objekt s prázdnými poli, prázdná diskuse objekt s prázdným polem.
null je třetí stav a plete se to nejsnáz ze všeho na téhle stránce.
Odkládací paměť si nemusíte psát — @patro/render má hotovou pro pravidla
(createRedirectMapCache), oblasti (createWidgetAreasCache), navigaci
(createMainMenuCache) i diskusi (createCommentsCache).