Webhook uz nic nevykonava v requestu. Zapise udalost do fronty a odpovi 202 do jednotek milisekund; strom vykona worker na pozadi. Za konektory nerucime, takze cekat na cizi sluzbu v requestu znamena ztracet udalosti pri timeoutu. Fronta ma opakovani s rostouci prodlevou (30 s, 2 min, 10 min, hodina), spravedlive poradi po firmach (jedna firma s tisicem udalosti nezablokuje ostatni), navrat zaseknutych behu po restartu a uklid hotovych. Marna chyba se neopakuje - chybejici skript za minutu existovat nezacne. Tri druhy spoustecu: push (webhook), vnitrni udalost (vznik a zmena ticketu) a pull, tedy pravidelne dotazovani u sluzeb bez webhooku (posta, zpravy). Planovac jen rekne "je cas", samotny dotaz je prvni krok stromu, takze ma zaznam v logu a opakuje se pri chybe jako cokoliv jineho. Kontrakt tela webhooku: kazdy parametr ma cestu (data.order.id, errors.0.message), takze jde napojit i odesilatel s vnorenym modelem. U adresy je metoda, ukazka tela a kopiruje se cela adresa vcetne domeny. Vnitrni kroky, ktere sahaji do naseho uloziste: ticket/upsert (zaloz nebo dopln podle externiho ID), assign-least-busy, assign-by-external, set-type, set-stage, add-tags, set-status, incident/create, flow/pause a flow/log. Faze ticketu jako treti osa vedle stavu a stitku. Stav je zivotni cyklus a pocitaji se z nej statistiky, faze je workflow daneho typu a muze byt jen jedna, takze se na ni da spolehnout v podmince. ID z cizich aplikaci u resitele: voicebot posle voicebotId a ticket skonci u toho, komu patri. Vazba je na jednom miste, ne v kazde automatizaci. Kazda chyba zaklada incident se dvema urovnemi: impact cte klient a je srozumitelny, detail cte admin a je v nem cely beh, ktery krok selhal, cele hlaseni a data na vstupu. Detail vidi jen spravce platformy. Ochrana proti smycce: automatizace navazana na zmenu ticketu ticket meni, cimz se spousti znovu - pri vyvoji to server polozilo. Resi to oznaceni behu pres AsyncLocalStorage a strop peti behu na jeden ticket za minutu. Upozorneni pri prideleni prace vcetne cisla u zalozky Tickety. Zivy dashboard: dlazdice nad nasimi daty na udalost, data z konektoru podle ttlSec s moznosti vynutit nacteni znovu. Opraveno: path a intervalSec u spoustece se pri ulozeni zahazovaly; nad seznamem neslo pouzit contains, takze na stitky neslo postavit podminku; novejsi vystup kroku ted prekryje starsi misto hlaseni konfliktu. Overeno dvema scenari proti bezicimu serveru, 34 kontrol: firma se skladem, expedici a IT, a hovory z voicebota (callSid do externiho ID, status do faze, prirazeni podle voicebotId, tri zpravy = jeden ticket se tremi udalostmi). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
9.9 KiB
9.9 KiB
Rejstřík znovupoužitelných funkcí a komponent
K čemu je co, aby se za rok nepsalo znovu něco, co už existuje. Když píšeš druhou funkci, která dělá skoro totéž jako něco odsud, je to skoro vždycky chyba - buď se má použít ta původní, nebo se má rozšířit.
Podrobný popis je vždycky v komentáři u samotné funkce. Tady je jen jedna věta a kdy to použít.
Ukládání dat (server)
Rozhoduje se na jednom místě, viz 14-databaze.md. Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
| Co | Kde | K čemu |
|---|---|---|
defineStore<T>(kind) |
src/data/store/index.ts |
Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
initStores({databaseReady}) |
src/data/store/index.ts |
Vybere režim. Volá se jednou při startu, nikde jinde. |
flushStores() |
src/data/store/index.ts |
Dopíše rozepsané zápisy. Jen při ukončení procesu. |
withCache(store) |
src/data/store/cached.ts |
Kopie v paměti pro konfigurační entity, které se čtou při každém requestu (uživatelé, role, firmy). Čte se synchronně, obnovuje se po zápisu. |
withMirror(store) |
src/data/store/mirror.ts |
Opačný směr než withCache: data se mění v paměti a po každé změně se celý záznam zapíše. Pro provozní data (tickety, automatizace, incidenty, rozložení). |
isVisible(entity, options) |
src/data/store/types.ts |
Vidí volající tenhle záznam? Prázdný seznam firem znamená "nic", ne "vše". |
nowIso() |
src/data/store/types.ts |
Časová značka. Ať se nepíše new Date().toISOString() na třiceti místech. |
memorySnapshot / fileSnapshot |
src/data/snapshot.ts |
Nižší vrstva pod createLocalStore: atomický zápis JSONu s debounce. Přímo se nepoužívá. |
db(), query, queryOne, transaction |
src/db/pool.ts |
Postgres. dbFor(tenantId) je připravený šev pro rozdělení na víc databází. |
seal, open, sealAll, openAll |
src/db/secretBox.ts |
Šifrování přístupových údajů konektorů (AES-256-GCM). Nic tajného se neukládá jinak. |
runMigrations() |
src/db/migrate.ts |
Migrace pod zámkem, jeden soubor = jedna transakce. |
Entity a práva (server)
| Co | Kde | K čemu |
|---|---|---|
crudRouter(options) |
src/routes/crud.ts |
Celý CRUD nad jednou entitou: seznam, detail, vytvoření, úprava, mazání, právo, audit. Nová entita v nastavení = jeden crudRouter, ne pět handlerů. |
readScope(req) |
src/routes/crud.ts |
Ze které firmy smí request číst. Povinný argument všech list volání. |
accessFor(user, tenantId?) |
src/data/access.ts |
Co uživatel smí: práva, záložky, výchozí firma. Klient si nic nedovozuje sám. |
permissionsOf(user, tenantId) |
src/data/permissions.ts |
Efektivní práva z rolí. Pětisekundová cache, invalidatePermissions() po zápisu. |
hasPermission(...) |
src/data/permissions.ts |
Jedna kontrola. Používá ji crudRouter i ruční handlery. |
navFor(...) |
src/data/tenantFeatures.ts |
Průnik toho, co firma má, a toho, na co má člověk právo. Navigace chodí ze serveru. |
recordAudit(input) |
src/data/audit.ts |
Zápis do auditu. Nevrací chybu a nečeká se - rozbitý audit nesmí rozbít aplikaci. |
enqueue(input) |
src/runtime/queue.ts |
Zařadí běh. Klíč proti dvojímu zařazení drží jeden běh na jednu událost. |
claimBatch(limit) |
src/runtime/queue.ts |
Vezme další práci, spravedlivě po firmách. Místo, kde nad Postgresem musí být SKIP LOCKED. |
onTicketEvent(kind, ticket) |
src/runtime/triggers.ts |
Změna ticketu zařadí navázané automatizace, včetně ochrany proti smyčce. |
withRun(marker, work) |
src/runtime/context.ts |
Označí, který běh práci způsobil. Bez toho automatizace spouští sama sebe. |
findBuiltinStep(...) |
src/runtime/builtinSteps.ts |
Kroky, které sahají do našeho úložiště, ne ven přes HTTP. |
findPersonByExternalId(...) |
src/data/people.ts |
Řešitel podle ID z cizí aplikace, například voicebotId. |
notify(input) |
src/data/notifications.ts |
Upozorní člověka. Nečeká se a nevyhazuje chyby, stejně jako audit. |
runFlow(steps, context, options) |
src/runtime/executor.ts |
Vykoná strom kroků. Nikdy nevyhodí výjimku, chyba je výsledek. Používá to akce na ticketu i webhook, aby se strom choval všude stejně. |
widgetCatalog(tenantIds, userId) |
src/data/widgets.ts |
Jediná definice toho, co jde položit na dashboard. Používá ji nabídka i kontrola ukládaného rozložení. |
intakeEvent(input) |
src/data/ticketStore.ts |
Přijme událost zvenku: podle externího ID buď založí ticket, nebo ji navěsí na existující. Jediná cesta, kterou se událost stává ticketem. |
getAgentStats(...) |
src/data/ticketStore.ts |
Výkon řešitelů: odbavené, mediány časů, vrácené, fronta. Používá to widget i detail osoby, aby čísla seděla. |
findByExternalId(...) |
src/data/ticketStore.ts |
Ticket firmy podle externího ID. Klíč je dvojice firma a ID. |
findByIntakeToken(token) |
src/data/tenants.ts |
Firma podle tokenu příjmu. Určuje i to, v jakém rozsahu je externí ID unikátní. |
refreshCaches() |
src/data/bootstrap.ts |
Obnoví všechny kopie v paměti. Volá se po zápisu, který je může změnit. |
bootstrapData({databaseReady}) |
src/data/bootstrap.ts |
Seznam všech entit a provozních dat. Nová entita se přidává tady, ne rozesetě po modulech. |
Skripty a konektory (server)
| Co | Kde | K čemu |
|---|---|---|
runScript(id, inputs, ctx) |
src/scripts/runner.ts |
Spustí skript. Nikdy nevyhodí výjimku, chybu vrací jako výsledek s celým hlášením. |
validateValues(...) |
src/scripts/values.ts |
Jedna kontrola pro vstupy i výstupy skriptu podle manifestu. |
scriptUtil |
src/scripts/util.ts |
Nádobíčko pro skripty: pick, first, num, date, need, get, applyRules, fillJson. Skript nemá sahat na nic jiného. |
createRedactor(...) |
src/scripts/util.ts |
Vyškrtá tajemství z textu před logováním. Používá se u všeho, co jde do logu. |
applyRules, fillJson |
src/scripts/mapping.ts |
Transformace dat: pole na pole s převody, nebo objekt na objekt. Viz 13-transformace-dat.md. |
getPath(obj, path) |
src/scripts/mapping.ts |
Čtení zakaznik.adresa.mesto z neznámého objektu. |
resolveTarget(...) |
src/scripts/connections.ts |
Z konektoru poskládá adresu a hlavičky. Přístupové údaje nikam jinam nevedou. |
createHttp(...) |
src/scripts/http.ts |
HTTP se timeoutem, limitem odpovědi a rozlišením "zkusit znovu" a "marné". |
scriptIdFor(serviceId, operationId) |
src/scripts/lookup.ts |
Který skript obsluhuje operaci z katalogu. |
Klient
| Co | Kde | K čemu |
|---|---|---|
EntityAdmin |
components/dashboard/EntityAdmin.tsx |
Celá správa jedné entity: tabulka, modál, validace, mazání. Nová záložka nastavení = popis sloupců a polí, ne nová stránka. |
parseJsonField |
components/dashboard/EntityAdmin.tsx |
Textové pole s JSONem na hodnotu, s hlášením, kde je chyba. |
TicketActions |
components/dashboard/TicketActions.tsx |
CTA akcí na ticketu plus typ, tagy a vlastní pole. Seznam akcí chodí ze serveru už vyfiltrovaný. |
ErrorDetail |
components/dashboard/ErrorDetail.tsx |
Rozbalovací celé chybové hlášení s kopírováním. Chyba se nikdy nezkracuje. |
CustomWidgetCard |
components/dashboard/widgets/CustomWidget.tsx |
Vykreslí widget, jehož data počítá server: číslo, pruhy, tabulka výkonu, časová řada, seznam, data z konektoru. |
TicketTable |
components/dashboard/TicketTable.tsx |
Tabulka ticketů pro všechna místa. Na mobilu se místo posouvání do strany kreslí karty. |
TicketEvents |
components/dashboard/TicketEvents.tsx |
Příchozí události ticketu včetně celého přijatého JSONu. |
ViewSwitch |
components/dashboard/ViewSwitch.tsx |
Přepínač tabulka nebo dlaždice. Používají ho všechny seznamy. |
FlowCanvas s start |
components/dashboard/flow/FlowCanvas.tsx |
Tentýž strom kroků i bez spouštěče - pro tělo akce, které spouští člověk. |
MappingEditor |
components/dashboard/flow/MappingEditor.tsx |
Editor transformací v obou režimech (pole na pole, JSON). |
DataState |
components/dashboard/DataState.tsx |
Načítání, chyba, prázdno. Ať to každá stránka nekreslí po svém. |
apiFetch<T> |
lib/api.ts |
Jediná cesta na API: base path, token, ApiError s celým hlášením ze serveru. |
useApiQuery<T> |
lib/useApiQuery.ts |
Načtení dat do stránky včetně reload. S body pošle POST (dávkové načtení), s enabled: false se neptá vůbec. |
cn(...) |
lib/cn.ts |
Skládání tříd. Podmíněné třídy nikdy ručně přes šablonu. |
format* |
lib/format.ts |
Čísla, procenta, datum, relativní čas, trvání. Formátování se nepíše v komponentě. |
serviceIcon(key) |
lib/serviceIcons.ts |
Klíč ikony ze serveru na komponentu. Server neposílá komponenty. |
usePageMeta |
lib/usePageMeta.ts |
Titulek stránky. |
Badge, Button, Modal, Card, ... |
components/ui/ |
Základní prvky. Nový vzhled tlačítka patří sem, ne do stránky. |
Pravidla, která z toho plynou
- Nová entita v nastavení:
defineStorev modulu entity, řádek vbootstrap.ts,crudRoutervsettings.ts, popis vSettings.tsx. Nic jiného se psát nemusí. - Data, která se mění za provozu, jdou přes
withMirror. Data, která se čtou při každém requestu a mění zřídka, přeswithCache. Obojí nikdy. - Chybu se nesmí zkracovat. Server vrací celé hlášení, klient ho umí
zobrazit (
ErrorDetail). - Klient nepočítá práva. Co smí, říká
accessFor.