snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-17 23:28:13 +05:00
commit e7da97194a
32688 changed files with 4208681 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
---
title: Osvědčené postupy
---
Tento dokument popisuje osvědčené postupy, které byste měli dodržovat při práci na backendu.
## Dodržujte modulární přístup
Backend sleduje modulární přístup, což je základní princip při práci s NestJS. Ujistěte se, že rozdělujete svůj kód na znovupoužitelné moduly, abyste udrželi čistý a organizovaný kód.
Každý modul by měl zahrnovat určitou funkci nebo funkcionalitu a mít jasně definovaný rozsah. Tento modulární přístup umožňuje jasné oddělení odpovědností a odstraňuje zbytečné složitosti.
## Zpřístupněte služby pro použití v modulech.
Vždy vytvářejte služby s jasně vymezenou jedinou odpovědností, což zvyšuje čitelnost a udržovatelnost kódu. Pojmenovávejte služby výstižně a konzistentně.
Měli byste také zpřístupnit služby, které chcete používat v jiných modulech. Zpřístupnění služeb ostatním modulům je možné díky výkonnému systému injektování závislostí v NestJS a podporuje volné vazby mezi komponentami.
## Vyhněte se použití typu `any`
Když deklarujete proměnnou jako `any`, kontrolor typů TypeScriptu neprovádí kontrolu typů, což umožňuje přiřadit proměnné jakýkoliv typ hodnot. TypeScript používá odvozování typů ke stanovení typu proměnné na základě hodnoty. Prohlášením jako `any` TypeScript již nemůže odvodit typ. To ztěžuje chycení chyb souvisejících s typy během vývoje, což vede k chybám za běhu a činí kód obtížně udržovatelným, méně spolehlivým a těžším na pochopení pro ostatní.
Proto by všechno mělo mít typ. Pokud tedy vytvoříte nový objekt se jménem a příjmením, měli byste vytvořit rozhraní nebo typ, který obsahuje jméno a příjmení a definuje tvar objektu, se kterým pracujete.

View File

@@ -0,0 +1,39 @@
---
title: Vlastní objekty
---
Objekty jsou struktury, které vám umožňují ukládat data (záznamy, atributy a hodnoty) specifická pro organizaci. Twenty poskytuje jak standardní, tak vlastní objekty.
Standardní objekty jsou vestavěné objekty se sadou atributů dostupných pro všechny uživatele. Příklady standardních objektů v Twenty zahrnují Firma a Osoba. Standardní objekty mají standardní pole, která jsou také dostupná pro všechny uživatele Twenty, jako například Firma.displayName.
Vlastní objekty jsou objekty, které můžete vytvářet pro ukládání informací specifických pro vaši organizaci. Nejsou vestavěné; členové vaší pracovní skupiny mohou vytvářet a upravovat vlastní objekty pro ukládání informací, které nejsou vhodné pro standardní objekty.
## Schema na vysoké úrovni
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Schema na vysoké úrovni" />
</div>
<br />
## Jak to funguje
Vlastní objekty pocházejí z tabulek metadat, které určují tvar, název a typ objektů. Veškeré tyto informace jsou přítomny v databázi schémat metadat, která se skládá z následujících tabulek:
* **Zdroj dat**: Definuje, kde se data nacházejí.
* **Objekt**: Popisuje objekt a odkazuje na DataSource.
* **Pole**: Vymezuje pole objektu a připojuje je k objektu.
Pro přidání vlastního objektu vyhledá člen pracovní skupiny rozhraní /metadata API. To aktualizuje metadata odpovídajícím způsobem a generuje GraphQL schéma na základě metadat, které se ukládá v GQL cache pro pozdější použití.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/add-custom-objects.jpeg" alt="Vyhledejte /metadata API pro přidání vlastních objektů" />
</div>
<br />
Pro načtení dat proces zahrnuje provádění dotazů přes /graphql endpoint a jejich předání přes Query Resolver.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/server/custom-object-schema.png" alt="Dotazujte se na /graphql endpoint pro načtení dat." />
</div>

View File

@@ -0,0 +1,46 @@
---
title: Příznaky funkcí
---
Příznaky funkcí slouží k skrytí zkušebních funkcí. Pro Twenty jsou nastaveny na úrovni pracovního prostoru, nikoli na úrovni uživatele.
## Přidání nového příznaku funkce
V `FeatureFlagKey.ts` přidat příznak funkce:
```ts
type FeatureFlagKey =
| 'IS_FEATURENAME_ENABLED'
| ...;
```
Také jej přidejte do výčtu v `feature-flag.entity.ts`:
```ts
enum FeatureFlagKeys {
IsFeatureNameEnabled = 'IS_FEATURENAME_ENABLED',
...
}
```
Pro uplatnění příznaku funkce na **backendové** funkci použijte:
```ts
@Gate({
featureFlag: 'IS_FEATURENAME_ENABLED',
})
```
Pro uplatnění příznaku funkce na **frontendové** funkci použijte:
```ts
const isFeatureNameEnabled = useIsFeatureEnabled('IS_FEATURENAME_ENABLED');
```
## Nakonfigurujte příznaky funkcí pro nasazení
Změnit odpovídající záznam v tabulce `core.featureFlag`:
| id | klíč | workspaceId | hodnota |
| ------- | ------------------- | ----------- | -------- |
| Náhodný | `IS_FUNKCEPOVOLENA` | WorkspaceID | `pravda` |

View File

@@ -0,0 +1,125 @@
---
title: Architektura složek
info: Podrobný pohled do struktury složek našeho serveru
---
Struktura adresářů backendu je následující:
```
server
└───ability
└───constants
└───core
└───database
└───decorators
└───filters
└───guards
└───health
└───integrations
└───metadata
└───workspace
└───utils
```
## Oprávnění
Definuje oprávnění a zahrnuje zpracovníky pro každou entitu.
## Dekorátory
Definuje vlastní dekorátory v NestJS pro přídavnou funkčnost.
Více podrobností naleznete ve [vlastních dekorátorech](https://docs.nestjs.com/custom-decorators).
## Filtry
Zahrnuje filtry výjimek k zpracování výjimek, které mohou nastat v koncových bodech GraphQL.
## Guards
Více podrobností naleznete ve [strážích](https://docs.nestjs.com/guards).
## Zdraví
Zahrnuje veřejně dostupné REST API (healthz), které vrací JSON k potvrzení, zda databáze funguje, jak se očekává.
## Metadata
Definuje vlastní objekty a poskytuje GraphQL API (graphql/metadata).
## Pracovní prostor
Generuje a poskytuje vlastní schéma GraphQL na základě metadat.
### Struktura adresáře pracovního prostoru
```
workspace
└───workspace-schema-builder
└───factories
└───graphql-types
└───database
└───interfaces
└───object-definitions
└───services
└───storage
└───utils
└───workspace-resolver-builder
└───factories
└───interfaces
└───workspace-query-builder
└───factories
└───interfaces
└───workspace-query-runner
└───interfaces
└───utils
└───workspace-datasource
└───workspace-manager
└───workspace-migration-runner
└───utils
└───workspace.module.ts
└───workspace.factory.spec.ts
└───workspace.factory.ts
```
Kořen adresáře pracovního prostoru zahrnuje `workspace.factory.ts`, soubor obsahující funkci `createGraphQLSchema`. Tato funkce generuje specifická schémata pracovních prostorů pomocí metadat pro přizpůsobení schématu individuálním pracovním prostorům. Oddělením konstrukce schématu a resolveru používáme funkci `makeExecutableSchema`, která kombinuje tyto různé prvky.
Tato strategie neslouží pouze k organizaci, ale pomáhá i v optimalizaci, jako je ukládání vytvořených typových definic do mezipaměti pro zvýšení výkonu a škálovatelnosti.
### Builder schématu pracovního prostoru
Generuje schéma GraphQL a zahrnuje:
#### Továrny:
Specializované konstruktory pro generování konstrukcí souvisejících s GraphQL.
* type.factory překládá metadata pole do typů GraphQL pomocí `TypeMapperService`.
* type-definition.factory vytváří vstupní nebo výstupní grafové objekty odvozené z `objectMetadata`.
#### GraphQL typy
Zahrnuje výčty, vstupy, objekty a skaláry a slouží jako stavební bloky pro konstrukci schématu.
#### Rozhraní a objektové definice
Obsahuje plány pro GraphQL entity a zahrnuje jak předdefinované, tak vlastní typy jako `MONEY` nebo `URL`.
#### Služby
Obsahuje službu, která spojuje FieldMetadataType s odpovídajícími GraphQL skalárami nebo modifikátory dotazů.
#### Úložiště
Zahrnuje třídu `TypeDefinitionsStorage`, která obsahuje opakovaně použitelné definice typů, zabraňující duplikaci typů GraphQL.
### Builder resolveru pracovního prostoru
Vytváří funkce pro řešení dotazů a mutací v GraphQL schématu.
Každá továrna v tomto adresáři je zodpovědná za produkci specifického typu resolveru, jako je továrna `FindManyResolverFactory`, navržená pro adaptabilní aplikaci na různé tabulky.
### Runner dotazů pracovního prostoru
Spouští vygenerované dotazy na databázi a analyzuje výsledek.

View File

@@ -0,0 +1,41 @@
---
title: Fronta zpráv
---
Fronty usnadňují provádění asynchronních operací. Mohou být použity pro provádění úkolů na pozadí, jako je například zaslání uvítacího e-mailu při registraci.
Každý případ použití bude mít svou vlastní třídu fronty rozšířenou z `MessageQueueServiceBase`.
Aktuálně podporujeme pouze `bull-mq`[bull-mq](https://bullmq.io/) jako ovladač fronty.
## Kroky pro vytvoření a použití nové fronty
1. Přidejte název fronty pro vaši novou frontu pod výčtem `MESSAGE_QUEUES`.
2. Poskytněte implementaci továrny fronty s názvem fronty jako token závislosti.
3. Injektujte frontu, kterou jste vytvořili, do požadovaného modulu/služby s názvem fronty jako token závislosti.
4. Přidejte třídu pracovníka s injekcí založenou na tokenu stejně jako producent.
### Ukázkové použití
```ts
class Resolver {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {}
async onSomeAction() {
//business logic
await this.queue.add(someData);
}
}
//async worker
class CustomWorker {
constructor(@Inject(MESSAGE_QUEUES.custom) private queue: MessageQueueService) {
this.initWorker();
}
async initWorker() {
await this.queue.work(async ({ id, data }) => {
//worker logic
});
}
}
```

View File

@@ -0,0 +1,87 @@
---
title: Příkazy backendu
icon: terminal
---
## Užitečné příkazy
Tyto příkazy by měly být vykonávány z adresáře packages/twenty-server.
Z jakéhokoli jiného adresáře můžete spustit `npx nx {command} twenty-server` (nebo `npx nx run twenty-server:{command}`).
### První nastavení
```
npx nx database:reset twenty-server # setup the database with dev seeds
```
### Spuštění serveru
```
npx nx run twenty-server:start
```
### Linter
```
npx nx run twenty-server:lint # pass --fix to fix lint errors
```
### Testovat
```
npx nx run twenty-server:test:unit # run unit tests
npx nx run twenty-server:test:integration # run integration tests
```
Poznámka: můžete spustit `npx nx run twenty-server:test:integration:with-db-reset`, pokud potřebujete před spuštěním integračních testů obnovit databázi.
### Obnovení databáze
Pokud chcete resetovat a naplnit databázi daty, můžete spustit následující příkaz:
```bash
npx nx run twenty-server:database:reset
```
### Migrace
#### Pro objekty ve schématech Core/Metadata (TypeORM)
```bash
npx nx run twenty-server:database:migrate:generate
```
## Technologický stack
Twenty primárně používá NestJS pro backend.
Prisma byl první ORM, který jsme použili. Ale aby uživatelé mohli vytvářet vlastní pole a vlastní objekty, dává větší smysl používat nižší úroveň, abychom mohli mít jemnou kontrolu. Projekt nyní používá TypeORM.
Takto nyní vypadá technologický stack.
**Jádro**
* [NestJS](https://nestjs.com/)
* [TypeORM](https://typeorm.io/)
* [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server)
**Databáze**
* [Postgres](https://www.postgresql.org/)
**Integrace třetích stran**
* [Sentry](https://sentry.io/welcome/) pro sledování chyb
**Testování**
* [Jest](https://jestjs.io/)
**Nástroje**
* [Yarn](https://yarnpkg.com/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
**Vývoj**
* [AWS EKS](https://aws.amazon.com/eks/)

View File

@@ -0,0 +1,85 @@
---
title: Zapier App
---
Snadno synchronizujte Twenty s více než 3000 aplikacemi pomocí [Zapier](https://zapier.com/). Automatizujte úkoly, zvyšte produktivitu a zlepšete vztahy se zákazníky!
## O Zapier
Zapier je nástroj, který umožňuje automatizovat pracovní postupy připojením aplikací, které váš tým používá každý den. Základním konceptem Zapier je automatizace pracovních postupů, nazývaných Zaps, které zahrnují spouštěče a akce.
Více o tom, jak Zapier funguje, se dozvíte [zde](https://zapier.com/how-it-works).
## Nastavení
### Krok 1: Nainstalujte balíčky Zapier
```bash
cd packages/twenty-zapier
yarn
```
### Krok 2: Přihlaste se pomocí CLI
Použijte své Zapier přihlašovací údaje k přihlášení pomocí CLI:
```bash
zapier login
```
### Krok 3: Nastavte proměnné prostředí
Z adresáře `packages/twenty-zapier` spusťte:
```bash
cp .env.example .env
```
Spusťte aplikaci lokálně, přejděte na [http://localhost:3000/settings/api-webhooks](http://localhost:3000/settings/api-webhooks), a vygenerujte klíč API.
Nahraďte hodnotu **YOUR_API_KEY** v souboru `.env` klíčem API, který jste právě vygenerovali.
## Vývoj
<Warning>
Ujistěte se, že před jakýmkoli příkazem `zapier` spustíte `yarn build`.
</Warning>
### Testovat
```bash
yarn test
```
### Linter
```bash
yarn format
```
### Sledování a kompilace při úpravě kódu
```bash
yarn watch
```
### Ověřte svou aplikaci Zapier
```bash
yarn validate
```
### Nasazení vaší aplikace Zapier
```bash
yarn deploy
```
### Seznam všech příkazů Zapier CLI
```bash
zapier
```

View File

@@ -0,0 +1,79 @@
---
title: Hlášení chyb, požadavky a pull requesty
icon: bug
info: Nahlašujte problémy, žádejte o nové funkce a přispívejte kódem
---
## Nahlášení chyb
Chcete-li nahlásit chybu, prosím [vytvořte issue na GitHubu](https://github.com/twentyhq/twenty/issues/new).
O pomoc můžete také požádat na [Discordu](https://discord.gg/cx5n4Jzs57).
## Požadavky na funkce
Pokud si nejste jisti, zda se jedná o chybu, a máte pocit, že je to spíše žádost o funkci, pak byste pravděpodobně měli [otevřít diskuzi](https://github.com/twentyhq/twenty/discussions/new).
## Odešlete pull request
Přispívání kódem do Twenty začíná pull requestem (PR).
### Než začnete
1. Zkontrolujte [existující issues](https://github.com/twentyhq/twenty/issues) ohledně související práce
2. Pro nové funkce nejprve otevřete issue k diskusi
3. Prostudujte si náš [Kodex chování](https://github.com/twentyhq/twenty/blob/main/.github/CODE_OF_CONDUCT.md)
### Fork a klonování
1. Vytvořte fork repozitáře na GitHubu
2. Naklonujte svůj fork:
```bash
git clone https://github.com/YOUR_USERNAME/twenty.git
cd twenty
```
3. Přidejte vzdálený upstream:
```bash
git remote add upstream https://github.com/twentyhq/twenty.git
```
### Vytvoření větve
```bash
git checkout -b feature/your-feature-name
```
Používejte výstižné názvy větví:
* `feature/add-export-button`
* `fix/login-redirect-issue`
* `docs/update-api-guide`
### Proveďte změny
1. Pište čistý, dobře zdokumentovaný kód
2. Dodržujte stávající styl kódu
3. Přidejte testy pro novou funkcionalitu
4. Podle potřeby aktualizujte dokumentaci
### Odešlete svůj PR
1. Odešlete svou větev:
```bash
git push origin feature/your-feature-name
```
2. Otevřete PR na GitHubu
3. Vyplňte šablonu PR
4. Propojte související issues
### Kontrolní seznam PR
* [ ] Kód odpovídá projektovým zásadám stylu
* [ ] Testy procházejí lokálně
* [ ] Dokumentace je aktualizována
* [ ] Popis PR vysvětluje změny

View File

@@ -0,0 +1,329 @@
---
title: Osvědčené postupy
icon: star
---
Tento dokument popisuje osvědčené postupy, které byste měli dodržovat při práci na frontend.
## Správa stavu
React a Jotai zajišťují správu stavu v kódu.
### Použijte atomy Jotai k ukládání stavu
Je dobrým zvykem vytvořit tolik atomů, kolik potřebujete ke správě stavu.
<Warning>
Je lepší použít více atomů než se snažit být příliš stručný s prop drilling.
</Warning>
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
export const MyComponent = () => {
const [myAtom, setMyAtom] = useAtomState(myAtomState);
return (
<div>
<input
value={myAtom}
onChange={(e) => setMyAtom(e.target.value)}
/>
</div>
);
}
```
### Nepoužívejte `useRef` k ukládání stavu
Vyhněte se používání `useRef` k ukládání stavu.
Pokud chcete ukládat stav, měli byste použít `useState` nebo atomy Jotai s `useAtomState`.
Podívejte se, jak spravovat překreslení, pokud máte pocit, že potřebujete `useRef`, abyste zabránili některým překreslením.
## Správa překreslení
Překreslení může být obtížné spravovat v Reactu.
Zde jsou některá pravidla, která je třeba dodržovat, abyste se vyhnuli zbytečnému překreslování.
Pamatujte, že můžete **vždy** zabránit opakovanému renderování pochopením jejich příčiny.
### Pracujte na úrovni kořene
Vyhýbání se překreslení v nových funkcích je nyní snadné tím, že je eliminujete na úrovni kořene.
Komponenta "PageChangeEffect" obsahuje pouze jeden "useEffect", který drží veškerou logiku pro provedení změny stránky.
Tímto způsobem víte, že existuje pouze jedno místo, které může spustit překreslování.
### Vždy si dvakrát rozmyslete před přidáním `useEffect` do vaší kódové databáze
Opakované renderování jsou často způsobeny zbytečným `useEffect`.
Měli byste si myslet, zda potřebujete `useEffect`, nebo zda můžete přesunout logiku ve funkci obsluhy událostí.
Pro vás je obecně snadné přesunout logiku do funkce `handleClick` nebo `handleChange`.
Můžete je také najít v knihovnách jako Apollo: `onCompleted`, `onError` atd.
### Použij sourozeneckou komponentu k extrakci `useEffect` nebo logiky načítání dat
Pokud máte pocit, že potřebujete přidat `useEffect` do základní komponenty, měli byste zvážit její extrakci v postranním vozíku.
Stejný postup můžete aplikovat na logiku získávání dat pomocí Apollo hooks.
```tsx
// ❌ Bad, will cause re-renders even if data is not changing,
// because useEffect needs to be re-evaluated
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <div>{data}</div>;
};
export const App = () => (
<PageComponent />
);
```
```tsx
// ✅ Good, will not cause re-renders if data is not changing,
// because useEffect is re-evaluated in another sibling component
export const PageComponent = () => {
const [data, setData] = useAtomState(dataState);
return <div>{data}</div>;
};
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [someDependency] = useAtomState(someDependencyState);
useEffect(() => {
if(someDependency !== data) {
setData(someDependency);
}
}, [someDependency]);
return <></>;
};
export const App = () => (
<>
<PageData />
<PageComponent />
</>
);
```
### Použijte rodiny atomů a selektory
Rodiny atomů a selektory jsou skvělým způsobem, jak se vyhnout překreslování.
Jsou užitečné, když potřebujete uložit seznam položek.
### Neměli byste používat `React.memo(MyComponent)`
Vyhněte se používání `React.memo()`, protože to neřeší příčinu překreslování, ale spíše přeruší řetězec překreslování, což může vést k neočekávanému chování a ztěžuje refaktorování kódu.
### Omezit použití `useCallback` nebo `useMemo`
Často nejsou nutné a kód ztěžují na čtení a údržbu pro nepodstatné zlepšení výkonu.
## Console.logs
Příkazy `console.log` jsou hodnotné při vývoji, protože poskytují pohled na hodnoty proměnných a průběh kódu v reálném čase. Ale, zanechání v produkčním kódu může vést k několika problémům:
1. **Výkon**: Přílišné logování může ovlivnit výkon za běhu, zejména u aplikací na straně klienta.
2. **Bezpečnost**: Logování citlivých dat může vystavit kritické informace komukoliv, kdo nahlédne do konzole prohlížeče.
3. **Čistota**: Naplnění konzole logy může zatížit důležitá varování nebo chyby, které je třeba vidět.
4. **Profesionalita**: Koneční uživatelé nebo klienti kontrolující konzolu a vidící množství logovacích příkazů mohou zpochybnit kvalitu a úroveň kódu.
Ujistěte se, že odstraníte všechny `console.logs`, než uvedete kód do produkce.
## Pojmenovávání
### Pojmenování proměnných
Jména proměnných by měla přesně zobrazovat účel nebo funkci proměnné.
#### Problém s generickými jmény
Generická jména v programování nejsou ideální, protože postrádají specifikaci, což vede k nejednoznačnosti a snižuje čitelnost kódu. Taková jména neposkytnou informace o účelu proměnné nebo funkce, čímž ztěžují vývojářům pochopení záměru kódu bez hlubšího zkoumání. To může vést ke zvýšené době ladění, vyšší náchylnosti k chybám a obtížím při údržbě a spolupráci. Mezitím, použití popisných jmen činí kód samozřejmým a snadněji navigovatelným, čímž se zvyšuje jeho kvalita a produktivita vývojáře.
```tsx
// ❌ Bad, uses a generic name that doesn't communicate its
// purpose or content clearly
const [value, setValue] = useState('');
```
```tsx
// ✅ Good, uses a descriptive name
const [email, setEmail] = useState('');
```
#### Některá slova, kterým se vyhnout v názvech proměnných
* dummy
### Obsluhovače událostí
Jména obsluhovačů událostí by měla začínat `handle`, zatímco `on` je prefix používaný k pojmenování událostí v komponentech props.
```tsx
// ❌ Bad
const onEmailChange = (val: string) => {
// ...
};
```
```tsx
// ✅ Good
const handleEmailChange = (val: string) => {
// ...
};
```
## Volitelné props
Vyhněte se tomu, abyste pro volitelný props předávali výchozí hodnotu.
**PŘÍKLAD**
Vezměte komponent `EmailField` definovanou níže:
```tsx
type EmailFieldProps = {
value: string;
disabled?: boolean;
};
const EmailField = ({ value, disabled = false }: EmailFieldProps) => (
<TextInput value={value} disabled={disabled} fullWidth />
);
```
**Použití**
```tsx
// ❌ Bad, passing in the same value as the default value adds no value
const Form = () => <EmailField value="username@email.com" disabled={false} />;
```
```tsx
// ✅ Good, assumes the default value
const Form = () => <EmailField value="username@email.com" />;
```
## Komponenta jako props
Pokuste se co nejvíce předávat neinstancované komponenty jako props, aby si děti mohly samy rozhodnout, které props potřebují předat.
Nejčastější příklad je komponenta ikon:
```tsx
const SomeParentComponent = () => <MyComponent Icon={MyIcon} />;
// In MyComponent
const MyComponent = ({ MyIcon }: { MyIcon: IconComponent }) => {
const theme = useTheme();
return (
<div>
<MyIcon size={theme.icon.size.md}>
</div>
)
};
```
Aby React pochopil, že komponenta je komponenta, musíte použít PascalCase, aby později ji bylo možné instancovat pomocí `<MyIcon>`
## Prop Drilling: Udržujte to minimální
Prop drilling, v kontextu React, odkazuje na praktiku předávání stavových proměnných a jejich setterů skrze mnoho vrstev komponent, i když mezilehlé komponenty je nepoužívají. Zatímco občas je to nutné, přehnané prop drilling může vést k:
1. **Snížená čitelnost**: Sledování původu nebo využití vlastnosti může být komplikované ve složitě vnořených strukturách komponent.
2. **Problémy s údržbou**: Změny v struktuře vlastností jedné komponenty mohou vyžadovat úpravy v některých komponentech, i když tyto vlastnosti přímo nepoužívají.
3. **Snížená znovupoužitelnost komponentů**: Komponent přijímající mnoho props pouze pro jejich předání, se stává méně univerzálním a obtížněji použitelným v různých kontextech.
Pokud máte pocit, že používáte přílišné prop drilling, podívejte se na osvědčené postupy správy stavu.
## Importy
Při importu, upřednostněte určené aliasy před upřesňováním úplných či relativních cest.
**Aliasy**
```js
{
alias: {
"~": path.resolve(__dirname, "src"),
"@": path.resolve(__dirname, "src/modules"),
"@testing": path.resolve(__dirname, "src/testing"),
},
}
```
**Použití**
```tsx
// ❌ Bad, specifies the entire relative path
import {
CatalogDecorator
} from '../../../../../testing/decorators/CatalogDecorator';
import {
ComponentDecorator
} from '../../../../../testing/decorators/ComponentDecorator';
```
```tsx
// ✅ Good, utilises the designated aliases
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from 'twenty-ui/testing';
```
## Validace schématu
[Zod](https://github.com/colinhacks/zod) je validátor schémat pro netypová data:
```js
const validationSchema = z
.object({
exist: z.boolean(),
email: z
.string()
.email('Email must be a valid email'),
password: z
.string()
.regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'),
})
.required();
type Form = z.infer<typeof validationSchema>;
```
## Zlomové změny
Vždy proveďte důkladné manuální testování, abyste zajistili, že úpravy nezpůsobily problémy jinde, zejména pokud testy zatím nebyly široce integrovány.

View File

@@ -0,0 +1,110 @@
---
title: Architektura složek
icon: folder-tree
info: Podrobný pohled na naši architekturu složek
---
V tomto průvodci prozkoumáte detaily struktury projektového adresáře a jak přispívá k organizaci a udržovatelnosti Twenty.
Následováním této konvence architektury složek je snazší najít soubory související s konkrétními funkcemi a zajistit rozšiřitelnost a udržovatelnost aplikace.
```
front
└───modules
│ └───module1
│ │ └───submodule1
│ └───module2
│ └───ui
│ │ └───display
│ │ └───inputs
│ │ │ └───buttons
│ │ └───...
└───pages
└───...
```
## Stránky
Zahrnuje komponenty nejvyšší úrovně definované aplikačními trasami. Importují více nízkoúrovňových komponent ze složky modulů (více podrobností níže).
## Moduly
Každý modul představuje funkci nebo skupinu funkcí s jejich specifickými komponenty, stavy a provozní logikou.
Všechny by měly dodržovat strukturu níže. Moduly můžete vnořovat do sebe (tzv. podmoduly) a platí pro ně stejná pravidla.
```
module1
└───components
│ └───component1
│ └───component2
└───constants
└───contexts
└───graphql
│ └───fragments
│ └───queries
│ └───mutations
└───hooks
│ └───internal
└───states
│ └───selectors
└───types
└───utils
```
### Kontexty
Kontext je způsob, jak předávat data skrz strom komponent, aniž by se musely na každé úrovni ručně předávat props.
Více podrobností naleznete v [React Context](https://react.dev/reference/react#context-hooks).
### GraphQL
Zahrnuje fragmenty, dotazy a mutace.
Více podrobností naleznete v [GraphQL](https://graphql.org/learn/).
* Fragmenty
Fragment je znovupoužitelný kus dotazu, který můžete použít na různých místech. Pomocí fragmentů je snazší vyhnout se duplikaci kódu.
Více podrobností naleznete v [GraphQL Fragments](https://graphql.org/learn/queries/#fragments).
* Dotazy
Více podrobností naleznete v [GraphQL Queries](https://graphql.org/learn/queries/).
* Mutace
Více podrobností naleznete v [GraphQL Mutations](https://graphql.org/learn/queries/#mutations).
### Hooky
Více podrobností naleznete v [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
### Stavy
Obsahuje logiku správy stavů. To řeší [Jotai](https://jotai.org).
* Selektory: Odvozené atomy (pomocí `createAtomSelector`) odvozují hodnoty z jiných atomů a jsou automaticky memoizovány.
Vestavěná správa stavů v Reactu stále spravuje stav uvnitř komponenty.
### Pomocné funkce
Měly by obsahovat pouze znovupoužitelné čisté funkce. V opačném případě vytvořte vlastní hooky ve složce `hooks`.
## UI
Obsahuje všechny znovupoužitelné komponenty UI použité v aplikaci.
Tato složka může obsahovat podsložky, jako jsou `data`, `display`, `feedback` a `input` pro specifické typy komponent. Každá komponenta by měla být samostatná a znovupoužitelná, takže ji můžete použít v různých částech aplikace.
Oddělením komponent UI od ostatních komponent ve složce `modules` je snazší udržovat konzistentní design a provádět změny v rozhraní, aniž by to ovlivnilo jiné části (obchodní logiku) kódové základny.
## Rozhraní a závislosti
Můžete importovat kód jiných modulů z jakéhokoliv modulu kromě složky `ui`. Díky tomu zůstane jeho kód snadno testovatelný.
### Interní
Každá část (hooky, stavy, ...) modulu může mít složku `internal`, která obsahuje části používané pouze v rámci modulu.

View File

@@ -0,0 +1,91 @@
---
title: Příkazy Frontend
icon: terminal
---
## Užitečné příkazy
### Spuštění aplikace
```bash
npx nx start twenty-front
```
### Regenerujte schéma GraphQL na základě API graphql schématu
```bash
npx nx run twenty-front:graphql:generate --configuration=metadata
```
NEBO
```bash
npx nx run twenty-front:graphql:generate
```
### Linter
```bash
npx nx run twenty-front:lint # pass --fix to fix lint errors
```
## Překlady
```bash
npx nx run twenty-front:lingui:extract
npx nx run twenty-front:lingui:compile
```
### Testovat
```bash
npx nx run twenty-front:test # run jest tests
npx nx run twenty-front:storybook:serve:dev # run storybook
npx nx run twenty-front:storybook:test # run tests # (needs yarn storybook:serve:dev to be running)
npx nx run twenty-front:storybook:coverage # (needs yarn storybook:serve:dev to be running)
```
## Technologický stack
Projekt má čistý a jednoduchý stack s minimálním počtem šablonových kódů.
**Aplikace**
* [React](https://react.dev/)
* [Apollo](https://www.apollographql.com/docs/)
* [GraphQL Codegen](https://the-guild.dev/graphql/codegen)
* [Jotai](https://jotai.org/)
* [TypeScript](https://www.typescriptlang.org/)
**Testování**
* [Jest](https://jestjs.io/)
* [Storybook](https://storybook.js.org/)
**Nástroje**
* [Yarn](https://yarnpkg.com/)
* [Craco](https://craco.js.org/docs/)
* [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)
## Architektura
### Směrování
[React Router](https://reactrouter.com/) zajišťuje směrování.
Aby se předešlo zbytečné [re-renders](/l/cs/developers/contribute/capabilities/frontend-development/best-practices-front#managing-re-renders) ) je všechna směrovací logika v `useEffect` v `PageChangeEffect`.
### Správa stavu
[Jotai](https://jotai.org/) zajišťuje správu stavu.
Podívejte se na [osvědčené postupy](/l/cs/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) pro více informací o správě stavu.
## Testování
[Jest](https://jestjs.io/) slouží jako nástroj pro testování jednotek, zatímco [Storybook](https://storybook.js.org/) je pro testování komponent.
Jest je primárně určen pro testování pomocných funkcí, nikoli samotných komponent.
Storybook slouží k testování chování izolovaných komponent, stejně jako k zobrazování návrhového systému.

View File

@@ -0,0 +1,178 @@
---
title: Klávesové zkratky
---
## Úvod
Pokud potřebujete poslouchat klávesovou zkratku, obvykle použijete posluchač událostí `onKeyDown`.
Ve `twenty-front` však můžete mít konflikty mezi stejnými klávesovými zkratkami, které jsou použity v různých komponentách, nasazených současně.
Například, pokud máte stránku, která poslouchá klávesu Enter, a modal, který poslouchá klávesu Enter, s komponentou Select uvnitř tohoto modalu, která poslouchá klávesu Enter, můžete mít konflikt, když jsou všechny nasazeny současně.
## Funkce hook `useScopedHotkeys`
K řešení tohoto problému máme vlastní hook, který umožňuje poslouchat klávesové zkratky bez jakéhokoliv konfliktu.
Umístíte ho do komponenty a bude poslouchat klávesové zkratky pouze když je komponenta nasazena A když je aktivní specifikovaný **hotkey scope**.
## Jak v praxi poslouchat klávesové zkratky?
Jsou potřebné dva kroky k nastavení poslouchání klávesových zkratek :
1. Nastavte [hotkey scope](#what-is-a-hotkey-scope-), který bude poslouchat klávesové zkratky
2. Použijte hook `useScopedHotkeys` k poslouchání klávesových zkratek
Nastavení oblastí klávesových zkratek je potřebné i na jednoduchých stránkách, protože jiné UI prvky jako levé menu nebo příkazové menu mohou také poslouchat klávesové zkratky.
## Použití klávesových zkratek
Obecně budete mít dva případy použití, které vyžadují klávesové zkratky :
1. Na stránce nebo v komponentě nasazené na stránce
2. V komponentě typu modal, která přebírá fokus kvůli akci uživatele
Druhý případ použití se může stát rekurzivně: například rozbalovací seznam v modalu.
### Poslouchání klávesových zkratek na stránce
Příklad :
```tsx
const PageListeningEnter = () => {
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
// 1. Set the hotkey scope in a useEffect
useEffect(() => {
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleEnterPage,
);
// Revert to the previous hotkey scope when the component is unmounted
return () => {
goBackToPreviousHotkeyScope();
};
}, [goBackToPreviousHotkeyScope, setHotkeyScopeAndMemorizePreviousScope]);
// 2. Use the useScopedHotkeys hook
useScopedHotkeys(
Key.Enter,
() => {
// Some logic executed on this page when the user presses Enter
// ...
},
ExampleHotkeyScopes.ExampleEnterPage,
);
return <div>My page that listens for Enter</div>;
};
```
### Poslouchání klávesových zkratek v komponentě typu modal
V tomto příkladu použijeme komponentu typu modal, která poslouchá klávesu Escape, aby svému nadřazenému komponentu řekla, že se má zavřít.
Zde interakce uživatele mění rozsah.
```tsx
const ExamplePageWithModal = () => {
const [showModal, setShowModal] = useState(false);
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const handleOpenModalClick = () => {
// 1. Set the hotkey scope when user opens the modal
setShowModal(true);
setHotkeyScopeAndMemorizePreviousScope(
ExampleHotkeyScopes.ExampleModal,
);
};
const handleModalClose = () => {
// 1. Revert to the previous hotkey scope when the modal is closed
setShowModal(false);
goBackToPreviousHotkeyScope();
};
return <div>
<h1>My page with a modal</h1>
<button onClick={handleOpenModalClick}>Open modal</button>
{showModal && <MyModalComponent onClose={handleModalClose} />}
</div>;
};
```
Pak v komponentě modalu :
```tsx
const MyDropdownComponent = ({ onClose }: { onClose: () => void }) => {
// 2. Use the useScopedHotkeys hook to listen for Escape.
// Note that escape is a common hotkey that could be used by many other components
// So it's important to use a hotkey scope to avoid conflicts
useScopedHotkeys(
Key.Escape,
() => {
onClose()
},
ExampleHotkeyScopes.ExampleModal,
);
return <div>My modal component</div>;
};
```
Je důležité použít tento vzor, když si nejste jisti, že pouhé použití useEffect s připojením/odpojením bude stačit k tomu, aby se předešlo konfliktům.
Tyto konflikty mohou být těžko laditelné a může se to stát častěji než s useEffects.
## Co je to hotkey scope?
Rozsah klávesových zkratek je řetězec, který představuje kontext, ve kterém jsou klávesové zkratky aktivní. Obecně je kódován jako enum.
Když změníte rozsah klávesových zkratek, klávesové zkratky, které poslouchají tento rozsah, budou povolené a klávesové zkratky, které poslouchají jiné rozsahy, budou zakázané.
Můžete nastavit pouze jeden rozsah najednou.
Například rozsahy klávesových zkratek pro každou stránku jsou definovány v `PageHotkeyScope` enumu:
```tsx
export enum PageHotkeyScope {
Settings = 'settings',
CreateWorkspace = 'create-workspace',
SignInUp = 'sign-in-up',
CreateProfile = 'create-profile',
PlanRequired = 'plan-required',
ShowPage = 'show-page',
PersonShowPage = 'person-show-page',
CompanyShowPage = 'company-show-page',
CompaniesPage = 'companies-page',
PeoplePage = 'people-page',
OpportunitiesPage = 'opportunities-page',
ProfilePage = 'profile-page',
WorkspaceMemberPage = 'workspace-member-page',
TaskPage = 'task-page',
}
```
Interně je aktuálně vybraný rozsah uložen v Jotai atomu, který je sdílen napříč aplikací :
```tsx
export const currentHotkeyScopeState = createState<HotkeyScope>({
key: 'currentHotkeyScopeState',
defaultValue: INITIAL_HOTKEYS_SCOPE,
});
```
Ale tento atom by se nikdy neměl spravovat ručně ! Ukážeme si, jak jej používat v příští sekci.
## Jak to funguje interně?
Vytvořili jsme tenkou vrstvu nad [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro), která je výkonnější a vyhýbá se zbytečným překreslením.
Také vytváříme Jotai atom, abychom mohli řídit stav rozsahu klávesových zkratek a učinit jej dostupným kdekoli v aplikaci.

View File

@@ -0,0 +1,8 @@
---
title: Storybook
description: Prozkoumejte knihovnu komponent UI na Twenty
---
Prohlédněte si naši kompletní knihovnu komponent a dokumentaci ve Storybooku.
[Otevřít Storybook →](https://storybook.twenty.com)

View File

@@ -0,0 +1,291 @@
---
title: Stylová příručka
icon: paintbrush
---
Tento dokument obsahuje pravidla pro psaní kódu.
Cílem zde je mít konzistentní kódovou základnu, která je snadno čitelná a snadno udržovatelná.
Pro toto je lepší být trochu více rozvláčný než být příliš stručný.
Vždy mějte na paměti, že lidé čtou kód častěji než ho píší, zejména u projektu s otevřeným zdrojovým kódem, kde může kdokoli přispět.
Existuje mnoho pravidel, která zde nejsou definována, ale která jsou automaticky kontrolována lintery.
## React
### Používejte funkcionální komponenty
Vždy používejte funkcionální komponenty TSX.
Nepoužívejte implicitní `import` s `const`, protože je obtížnější číst a importovat s automatickým dokončováním kódu.
```tsx
// ❌ Bad, harder to read, harder to import with code completion
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good, easy to read, easy to import with code completion
export function MyComponent() {
return <div>Hello World</div>;
};
```
### Vlastnosti
Vytvořte typ rekvizit a pojmenujte jej `(ComponentName)Props`, pokud není potřeba jej exportovat.
Použijte destrukturalizaci rekvizit.
```tsx
// ❌ Bad, no type
export const MyComponent = (props) => <div>Hello {props.name}</div>;
// ✅ Good, type
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
#### Upusťte od používání `React.FC` nebo `React.FunctionComponent` k definování typů rekvizit
```tsx
/* ❌ - Bad, defines the component type annotations with `FC`
* - With `React.FC`, the component implicitly accepts a `children` prop
* even if it's not defined in the prop type. This might not always be
* desirable, especially if the component doesn't intend to render
* children.
*/
const EmailField: React.FC<{
value: string;
}> = ({ value }) => <TextInput value={value} disabled fullWidth />;
```
```tsx
/* ✅ - Good, a separate type (OwnProps) is explicitly defined for the
* component's props
* - This method doesn't automatically include the children prop. If
* you want to include it, you have to specify it in OwnProps.
*/
type EmailFieldProps = {
value: string;
};
const EmailField = ({ value }: EmailFieldProps) => (
<TextInput value={value} disabled fullWidth />
);
```
#### Nepoužívejte rozbalení jediného objektu rekvizit v prvcích JSX
Vyhněte se používání rozbalení jediného objektu rekvizit v prvcích JSX, například `{...props}`. Tato praxe často vede k tomu, že kód je méně čitelný a obtížnější udržovat, protože není jasné, které rekvizity komponenta přijímá.
```tsx
/* ❌ - Bad, spreads a single variable prop into the underlying component
*/
const MyComponent = (props: OwnProps) => {
return <OtherComponent {...props} />;
}
```
```tsx
/* ✅ - Good, Explicitly lists all props
* - Enhances readability and maintainability
*/
const MyComponent = ({ prop1, prop2, prop3 }: MyComponentProps) => {
return <OtherComponent {...{ prop1, prop2, prop3 }} />;
};
```
Odůvodnění:
* Na první pohled je jasné, které prop kód předává, čímž je snazší pochopit a udržovat.
* Pomáhá to předcházet těsnému provázání mezi komponentami prostřednictvím jejich rekvizit.
* Lintovací nástroje usnadňují identifikaci překlepů nebo nepoužívaných rekvizit, když rekvizity uvádíte explicitně.
## JavaScript
### Používejte operátor nullish-coalescing `??`
```tsx
// ❌ Bad, can return 'default' even if value is 0 or ''
const value = process.env.MY_VALUE || 'default';
// ✅ Good, will return 'default' only if value is null or undefined
const value = process.env.MY_VALUE ?? 'default';
```
### Používejte volitelné zřetězení `?.`
```tsx
// ❌ Bad
onClick && onClick();
// ✅ Good
onClick?.();
```
## TypeScript
### Používejte `type` místo `interface`
Vždy používejte `type` místo `interface`, protože se téměř vždy překrývají a `type` je flexibilnější.
```tsx
// ❌ Bad
interface MyInterface {
name: string;
}
// ✅ Good
type MyType = {
name: string;
};
```
### Používejte textové literály místo výčtů
[Textové literály](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) jsou preferovaný způsob pro zpracování hodnot podobných výčtům v TypeScriptu. Snadněji se rozšiřují pomocí Pick a Omit a nabízejí lepší vývojářskou zkušenost, zejména s automatickým dokončováním kódu.
Proč TypeScript doporučuje vyhnout se výčtům zjistíte [zde](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#enums).
```tsx
// ❌ Bad, utilizes an enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
let color = Color.Red;
```
```tsx
// ✅ Good, utilizes a string literal
let color: "red" | "green" | "blue" = "red";
```
#### GraphQL a interní knihovny
Měli byste používat výčty, které generuje kód generátor GraphQL.
Je také lepší používat výčet při používání interní knihovny, aby interní knihovna nemusela vystavovat textový typ neusouvisející s interním API.
Příklad:
```TSX
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
```
## Styling
### Používejte StyledComponents
Stylujte komponenty pomocí [Linaria styled](https://github.com/callstack/linaria).
```tsx
// ❌ Bad
<div className="my-class">Hello World</div>
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
Prefixujte stylizované komponenty "Styled", abyste je odlišili od "skutečných" komponent.
```tsx
// ❌ Bad
const Title = styled.div`
color: red;
`;
```
```tsx
// ✅ Good
const StyledTitle = styled.div`
color: red;
`;
```
### Theming
Využití tématu pro většinu stylování komponent je preferovaný přístup.
#### Jednotky měření
Vyhýbejte se používání hodnot `px` nebo `rem` přímo ve stylizovaných komponentách. Potřebné hodnoty jsou obvykle již definovány v tématu, takže se doporučuje využívat je pro tyto účely.
#### Barvy
Zdržte se zavádění nových barev; místo toho použijte existující paletu z tématu. Pokud by došlo k tomu, že paleta neodpovídá, prosím nechte komentář, aby to tým mohl napravit.
```tsx
// ❌ Bad, directly specifies style values without utilizing the theme
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
font-weight: 400;
margin-left: 4px;
border-radius: 50px;
`;
```
```tsx
// ✅ Good, utilizes the theme
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
margin-left: ${({ theme }) => theme.spacing(1)};
border-radius: ${({ theme }) => theme.border.rounded};
`;
```
## Prosazování Zákazu Importů Typů
Vyhýbejte se typovým importům. K prosazení tohoto standardu pravidlo Oxlint kontroluje a hlásí jakékoli typové importy. To pomáhá udržovat konzistenci a čitelnost v TypeScript kódu.
```tsx
// ❌ Bad
import { type Meta, type StoryObj } from '@storybook/react';
// ❌ Bad
import type { Meta, StoryObj } from '@storybook/react';
// ✅ Good
import { Meta, StoryObj } from '@storybook/react';
```
### Proč Zákaz Importů Typů
* **Konzistence**: Vyhýbáním se importům typů a použitím jednotného přístupu pro importy typů i hodnot zůstává kódová základna konzistentní ve svém stylu importů modulů.
* **Čitelnost**: Importy bez type zlepšují čitelnost kódu tím, že je jasné, zda importujete hodnoty nebo typy. Tím se snižuje dvojznačnost a usnadňuje pochopení účelu importovaných symbolů.
* **Udržovatelnost**: Zvyšuje udržovatelnost kódové základny, protože vývojáři mohou při revizi nebo úpravách kódu identifikovat a najít importy pouze typů.
### Pravidlo Oxlint
Pravidlo Oxlint, `typescript/consistent-type-imports`, prosazuje standard zákazu importů typů. Toto pravidlo generuje chyby nebo varování pro jakékoli porušení typového importu.
Upozorňujeme, že toto pravidlo konkrétně řeší vzácné okrajové případy, kdy dochází k neúmyslným typovým importům. TypeScript sám odrazuje tuto praxi, jak je uvedeno v [poznámkách k verzi TypeScript 3.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html). Ve většině případů byste neměli potřebovat používat pouze typové importy.
Aby váš kód splňoval toto pravidlo, spusťte Oxlint jako součást svého vývojového pracovního postupu.

View File

@@ -0,0 +1,61 @@
---
title: Práce s Figma
info: Zjistěte, jak můžete spolupracovat s Figmou Twenty
---
Figma je nástroj pro návrh uživatelského rozhraní, který pomáhá překlenout komunikační bariéru mezi návrháři a vývojáři.
Tento průvodce vysvětluje, jak můžete spolupracovat s Figma.
## Přístup
1. **Přístup ke sdílenému odkazu:** K Figma souboru projektu můžete přistoupit [zde](https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty).
2. **Přihlásit se:** Pokud ještě nejste přihlášeni, Figma vás k tomu vyzve.
Klíčové funkce jsou dostupné pouze přihlášeným uživatelům, jako je režim pro vývojáře a možnost výběru dedikovaného rámce.
<Warning>
Nebudete moci účinně spolupracovat bez účtu.
</Warning>
## Struktura Figma
Na levém postranním panelu můžete přistupovat k různým stránkám Figmy společnosti Twenty. Takto jsou organizovány:
* **Stránka komponentů:** Toto je první stránka. Návrhář ji používá k vytváření a organizování znovupoužitelných návrhových prvků použitých v celém návrhovém souboru. Například tlačítka, ikony, symboly nebo jakékoli jiné znovupoužitelné komponenty. Slouží k zajištění konzistence v celém návrhu.
* **Hlavní stránka:** Druhá stránka je hlavní stránka, která zobrazuje kompletní uživatelské rozhraní projektu. Můžete stisknout **"Přehrát"**, abyste použili kompletní prototyp aplikace.
* **Stránky funkcí:** Ostatní stránky jsou obvykle věnovány funkcím ve vývoji. Obsahují návrh specifických funkcí nebo modulů aplikace či webu. Typicky se stále vyvíjejí.
## Užitečné tipy
S přístupem pouze pro čtení nemůžete návrh upravovat, ale máte přístup ke všem funkcím užitečným pro přeměnu návrhu na kód.
### Použití režimu pro vývojáře
Dev Mode Figma zvyšuje produktivitu vývojářů díky snadné navigaci designem, efektivnímu spravování prvků, efektivním komunikačním nástrojům, integracím do nástrojů, rychlým částem kódu a klíčovým informacím o vrstvě, čímž překonává mezeru mezi návrhem a vývojem. O Dev Mode se můžete dozvědět více [zde](https://www.figma.com/dev-mode/).
Přepněte na režim „Vývojář“ v pravé části nástrojové lišty, kde si můžete prohlédnout specifikace návrhu, kopírovat CSS a přistupovat k prvkům.
### Použití prototypu
Klikněte na libovolný prvek na plátně a stiskněte tlačítko „Přehrát“ v pravém horním rohu rozhraní k zobrazení prototypu. Režim prototypu vám umožňuje interakci s návrhem, jakoby to byl finální produkt. Ukazuje tok mezi obrazovkami a jak se prvky uživatelského rozhraní jako tlačítka, odkazy nebo menu chovají při interakci.
1. **Pochopení přechodů a animací:** V prototypovém režimu můžete vidět libovolné přechody nebo animace, které návrhář přidal mezi obrazovky nebo prvky UI, což poskytuje jasné vizuální instrukce vývojářům o zamýšleném chování a stylu.
2. **Objasnění implementace:** Prototyp může také pomoci snížit nejasnosti. Vývojáři s ním mohou interagovat pro lepší pochopení funkčnosti či vzhledu konkrétních prvků.
Pro podrobnější detaily a pokyny k Figma platformě můžete navštívit oficiální [Figma Documentation](https://help.figma.com/hc/en-us).
### Měření vzdálenosti
Vyberte prvek, podržte klávesu `Option` (Mac) nebo `Alt` (Windows) a pak přejeďte kurzorem nad jiným prvkem, abyste viděli vzdálenost mezi nimi.
### Rozšíření Figma pro VSCode (doporučeno)
[Figma pro VS Code](https://marketplace.visualstudio.com/items?itemName=figma.figma-vscode-extension)
vám umožňuje procházet a zkoumat návrhové soubory, spolupracovat s návrháři, sledovat změny a zrychlit implementaci – vše bez opuštění vašeho textového editoru.
Je součástí našich doporučených rozšíření.
## Spolupráce
1. **Použití komentářů:** Jste vítáni použít funkci komentářů kliknutím na bublinu vlevo na nástrojové liště.
2. **Rozhovor prostřednictvím kurzoru:** Hezkou funkcí Figma je Rozhovor prostřednictvím kurzoru. Stačí stisknout `;` na Macu a `/` ve Windows a odeslat zprávu, pokud vidíte, že někdo jiný používá Figma současně s vámi.

View File

@@ -0,0 +1,317 @@
---
title: Místní nastavení
icon: laptop-code
description: Průvodce pro přispěvatele (nebo zvídavé vývojáře), kteří chtějí spustit Twenty lokálně.
---
## Předpoklady
<Tabs>
<Tab title="Linux a macOS">
Než nainstalujete a použijete Twenty, ujistěte se, že máte na svém počítači nainstalovány následující balíčky:
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node v24.5.0](https://nodejs.org/en/download)
* [yarn v4](https://yarnpkg.com/getting-started/install)
* [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
<Warning>
`npm` nebude fungovat, měli byste místo něj použít `yarn`. Yarn je nyní součástí Node.js, takže jej nemusíte instalovat samostatně.
Musíte pouze spustit `corepack enable`, abyste povolili Yarn, pokud jste to ještě neudělali.
</Warning>
</Tab>
<Tab title="Windows (WSL)">
1. Nainstalujte WSL
Otevřete PowerShell jako správce a spusťte:
```powershell
wsl --install
```
Nyní byste měli vidět výzvu k restartování počítače. Pokud ne, restartujte jej ručně.
Při restartu se otevře okno PowerShellu a nainstaluje Ubuntu. Tento proces může chvíli trvat.
Zobrazí se výzva k vytvoření uživatelského jména a hesla pro vaši instalaci Ubuntu.
2. Nainstalujte a nastavte git
```bash
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "youremail@domain.com"
```
3. Nainstalujte nvm, node.js a yarn
<Warning>
Použijte `nvm` pro instalaci správné verze `node`. `nvmrc` zajišťuje, že všichni přispěvatelé používají stejnou verzi.
</Warning>
```bash
sudo apt-get install curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
```
Zavřete a znovu otevřete terminál, abyste použili nvm. Potom spusťte následující příkazy.
```bash
nvm install # installs recommended node version
nvm use # use recommended node version
corepack enable
```
</Tab>
</Tabs>
---
## Krok 1: Git Clone
V terminálu spusťte následující příkaz.
<Tabs>
<Tab title="SSH (Doporučeno)">
Pokud jste ještě nenastavili SSH klíče, můžete se naučit, jak to udělat, [zde](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh).
```bash
git clone git@github.com:twentyhq/twenty.git
```
</Tab>
<Tab title="HTTPS">
```bash
git clone https://github.com/twentyhq/twenty.git
```
</Tab>
</Tabs>
## Krok 2: Umístěte se na kořen
```bash
cd twenty
```
Všechny příkazy v následujících krocích byste měli provádět z kořene projektu.
## Krok 3: Nastavení PostgreSQL databáze
<Tabs>
<Tab title="Linux">
**Možnost 1 (doporučeno):** Pro lokální vytvoření databáze:
Použijte následující odkaz pro instalaci PostgreSQL na vašem linuxovém stroji: [Instalace PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Poznámka: Možná budete potřebovat přidat `sudo -u postgres` k příkazu před `psql`, abyste předešli chybám s oprávněními.
**Možnost 2:** Pokud máte nainstalován docker:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Mac OS">
**Možnost 1 (doporučeno):** Pro lokalní vytvoření databáze pomocí `brew`:
```bash
brew install postgresql@16
export PATH="/opt/homebrew/opt/postgresql@16/bin:$PATH"
brew services start postgresql@16
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Můžete zjistit, zda PostgreSQL server běží, spuštěním:
```bash
brew services list
```
Instalátor nemusí ve výchozím nastavení vytvořit uživatele `postgres` při instalaci
přes Homebrew na macOS. Místo toho vytvoří PostgreSQL roli, která odpovídá vašemu uživatelskému jménu v MacOS (např. "john").
Pro zkontrolování a vytvoření uživatele `postgres`, pokud je to nutné, postupujte takto:
```bash
# Connect to PostgreSQL
psql postgres
or
psql -U $(whoami) -d postgres
```
Po zobrazení výzvy psql (postgres=#) spusťte:
```bash
# List existing PostgreSQL roles
\du
```
Zobrazí se výstup podobný tomuto:
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
john | Superuser | {}
```
Pokud nevidíte roli `postgres`, pokračujte na další krok.
Vytvořte uživatelskou roli `postgres` ručně:
```bash
CREATE ROLE postgres WITH SUPERUSER LOGIN;
```
Tím vytvoříte superuživatelskou roli pojmenovanou `postgres` s přístupovými právy.
```bash
Role name | Attributes | Member of
-----------+-------------+-----------
postgres | Superuser | {}
john | Superuser | {}
```
**Možnost 2:** Pokud máte nainstalován docker:
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
Všechny následující kroky je třeba provádět v terminálu WSL (v rámci vaší virtuálního stroje).
**Možnost 1:** Pro lokální zprovoznění PostgreSQL:
Použijte následující odkaz pro instalaci PostgreSQL na vašem linuxovém virtuálním stroji: [Instalace PostgreSQL](https://www.postgresql.org/download/linux/)
```bash
psql postgres -c "CREATE DATABASE \"default\";" -c "CREATE DATABASE test;"
```
Poznámka: Možná budete potřebovat přidat `sudo -u postgres` k příkazu před `psql`, abyste předešli chybám s oprávněními.
**Možnost 2:** Pokud máte nainstalován docker:
Používání Dockeru na WSL přidává další vrstvu složitosti.
Použijte tuto možnost pouze pokud si jste jisti extra kroky včetně zapnutí [Docker Desktop WSL2](https://docs.docker.com/desktop/wsl).
```bash
make -C packages/twenty-docker postgres-on-docker
```
</Tab>
</Tabs>
Nyní můžete přistupovat k databázi na adrese `localhost:5432`.
Pokud jste použili výše uvedenou možnost Dockeru, výchozí přihlašovací údaje jsou uživatelské jméno `postgres` a heslo `postgres`. U nativních instalací PostgreSQL použijte přihlašovací údaje a role nakonfigurované na vašem počítači.
## Krok 4: Nastavení Redis databáze (cache)
Twenty vyžaduje ke svému provozu Redis cache pro zajištění nejlepšího výkonu.
<Tabs>
<Tab title="Linux">
**Možnost 1:** Pro lokalní vytvoření Redis:
Použijte následující odkaz pro instalaci Redis na vašem Linuxovém stroji: [Instalace Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Možnost 2:** Pokud máte nainstalován docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Mac OS">
**Možnost 1 (doporučeno):** Pro lokalní vytvoření Redis pomocí `brew`:
```bash
brew install redis
```
Spusťte svůj server Redis:
```bash
brew services start redis
```
**Možnost 2:** Pokud máte nainstalován docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
<Tab title="Windows (WSL)">
**Možnost 1:** Pro lokalní vytvoření Redis:
Použijte následující odkaz pro instalaci Redis na vašem Linuxovém virtuálním stroji: [Instalace Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/)
**Možnost 2:** Pokud máte nainstalován docker:
```bash
make -C packages/twenty-docker redis-on-docker
```
</Tab>
</Tabs>
Pokud potřebujete GUI klienta, doporučujeme [Redis Insight](https://redis.io/insight/) (k dispozici je bezplatná verze).
## Krok 5: Nastavení proměnných prostředí
Použijte proměnné prostředí nebo `.env` soubory, abyste nakonfigurovali váš projekt. Více informací [zde](/l/cs/developers/self-host/capabilities/setup).
Zkopírujte soubory `.env.example` v `/front` a `/server`:
```bash
cp ./packages/twenty-front/.env.example ./packages/twenty-front/.env
cp ./packages/twenty-server/.env.example ./packages/twenty-server/.env
```
<Info>
**Režim více pracovních prostorů:** Ve výchozím nastavení běží Twenty v režimu jednoho pracovního prostoru, ve kterém lze vytvořit pouze jeden pracovní prostor. Chcete-li povolit podporu více pracovních prostorů (užitečné pro testování funkcí založených na subdoménách), nastavte v serverovém souboru `.env` hodnotu `IS_MULTIWORKSPACE_ENABLED=true`. Podrobnosti viz [Režim více pracovních prostorů](/l/cs/developers/self-host/capabilities/setup#multi-workspace-mode).
</Info>
## Krok 6: Instalace závislostí
Pro sestavení Twenty serveru a zavedení některých dat do vaší databáze spusťte následující příkaz:
```bash
yarn
```
Všimněte si, že `npm` nebo `pnpm` nebudou fungovat.
## Krok 7: Spuštění projektu
<Tabs>
<Tab title="Linux">
V závislosti na vaší Linuxové distribuci mohl být Redis server spuštěn automaticky.
Pokud ne, zkontrolujte [instalační průvodce Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) pro vaši distribuci.
</Tab>
<Tab title="Mac OS">
Redis by již měl být spuštěn. Pokud ne, spusťte:
```bash
brew services start redis
```
</Tab>
<Tab title="Windows (WSL)">
V závislosti na vaší Linuxové distribuci mohl být Redis server spuštěn automaticky.
Pokud ne, zkontrolujte [instalační průvodce Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) pro vaši distribuci.
</Tab>
</Tabs>
Nastavte svou databázi pomocí následujícího příkazu:
```bash
npx nx database:reset twenty-server
```
Spusťte server, pracovní proces a frontendové služby:
```bash
npx nx start twenty-server
npx nx worker twenty-server
npx nx start twenty-front
```
Alternativně můžete spustit všechny služby najednou:
```bash
npx nx start
```
## Krok 8: Použijte Twenty
**Frontend**
Frontend Twenty bude spuštěný na [http://localhost:3001](http://localhost:3001).
Můžete se přihlásit pomocí výchozího demo účtu: `tim@apple.dev` (heslo: `tim@apple.dev`)
**Backend**
* Server Twenty poběží na [http://localhost:3000](http://localhost:3000)
* K GraphQL API se dostanete na [http://localhost:3000/graphql](http://localhost:3000/graphql)
* K REST API je přístup na [http://localhost:3000/rest](http://localhost:3000/rest)
## Řešení potíží
Pokud narazíte na jakýkoli problém, zkontrolujte sekci [Řešení potíží](/l/cs/developers/self-host/capabilities/troubleshooting) pro možná řešení.

View File

@@ -0,0 +1,77 @@
---
title: Příkazy
icon: terminal
description: Užitečné příkazy pro vývoj Twenty.
---
Příkazy je možné spouštět z kořene repozitáře pomocí `npx nx`. Pro explicitní cílení použijte `npx nx run {project}:{command}`.
## Spuštění aplikace
```bash
npx nx start twenty-front # Frontend dev server (http://localhost:3001)
npx nx start twenty-server # Backend server (http://localhost:3000)
npx nx run twenty-server:worker # Background worker
```
## Databáze
```bash
npx nx database:reset twenty-server # Reset and seed database
npx nx run twenty-server:database:migrate:prod # Run migrations
npx nx run twenty-server:database:migrate:generate --name <name> --type <fast|slow> # Generate a migration
```
## Lintování
```bash
npx nx lint:diff-with-main twenty-front # Lint changed files (fastest)
npx nx lint:diff-with-main twenty-server
npx nx lint twenty-front --configuration=fix # Auto-fix
```
## Kontrola typů
```bash
npx nx typecheck twenty-front
npx nx typecheck twenty-server
```
## Testování
```bash
# Frontend
npx nx test twenty-front # Jest unit tests
npx nx storybook:build twenty-front # Build Storybook
npx nx storybook:test twenty-front # Storybook tests
# Backend
npx nx run twenty-server:test:unit # Unit tests
npx nx run twenty-server:test:integration # Integration tests
npx nx run twenty-server:test:integration:with-db-reset # Integration with DB reset
# Single file (fastest)
npx jest path/to/test.test.ts --config=packages/{project}/jest.config.mjs
```
## GraphQL
```bash
npx nx run twenty-front:graphql:generate # Regenerate types
npx nx run twenty-front:graphql:generate --configuration=metadata # Metadata schema
```
## Překlady
```bash
npx nx run twenty-front:lingui:extract # Extract strings
npx nx run twenty-front:lingui:compile # Compile translations
```
## Sestavení
```bash
npx nx build twenty-shared # Must be built first
npx nx build twenty-front
npx nx build twenty-server
```

View File

@@ -0,0 +1,31 @@
---
title: Přispět
description: Přispějte k open-source vývoji Twenty.
---
<Frame>
<img src="/images/user-guide/github/github-header.png" alt="AI" />
</Frame>
## Přehled
Twenty je open-source a vítá příspěvky od komunity. Ať už opravujete chyby, přidáváte funkce nebo zlepšujete dokumentaci, vaše příspěvky pomáhají zlepšovat Twenty pro všechny.
## Způsoby, jak přispět
* **Nahlašujte chyby**: Pomozte identifikovat a dokumentovat problémy
* **Přidávejte funkce**: Navrhujte a implementujte novou funkcionalitu
* **Zlepšujte dokumentaci**: Udělejte naši dokumentaci přehlednější a užitečnější
* **Vývoj frontendu**: Pracujte na uživatelském rozhraní postaveném na Reactu
* **Vývoj backendu**: Podílejte se na serveru NestJS
## Začínáme
<CardGroup cols={2}>
<Card title="Hlášení chyb a požadavky" icon="bug" href="/l/cs/developers/contribute/capabilities/bug-and-requests">
Nahlaste problémy nebo požádejte o nové funkce
</Card>
<Card title="Vývoj frontendu" icon="browser" href="/l/cs/developers/contribute/capabilities/frontend-development/folder-architecture-front">
Přispějte k vývoji uživatelského rozhraní
</Card>
</CardGroup>

View File

@@ -0,0 +1,176 @@
---
title: Stylová příručka
icon: paintbrush
description: Konvence kódu a osvědčené postupy pro přispívání do Twenty.
---
## React
### Pouze funkcionální komponenty
Vždy používejte funkcionální komponenty TSX s pojmenovanými exporty.
```tsx
// ❌ Bad
const MyComponent = () => {
return <div>Hello World</div>;
};
export default MyComponent;
// ✅ Good
export function MyComponent() {
return <div>Hello World</div>;
};
```
### Vlastnosti
Vytvořte typ s názvem `{ComponentName}Props`. Používejte destrukturalizaci. Nepoužívejte `React.FC`.
```tsx
type MyComponentProps = {
name: string;
};
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
```
### Nepoužívejte prop spreading jediné proměnné
```tsx
// ❌ Bad
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
// ✅ Good
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
```
## Správa stavu
### Atomy Jotai pro globální stav
```tsx
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
export const myAtomState = createAtomState<string>({
key: 'myAtomState',
defaultValue: 'default value',
});
```
* Upřednostňujte atomy před prop drillingem
* Nepoužívejte `useRef` pro stav — použijte `useState` nebo atomy
* Používejte rodiny atomů a selektory pro seznamy
### Vyhněte se zbytečnému opakovanému vykreslování
* Přesuňte `useEffect` a načítání dat do sesterských sidecar komponent
* Upřednostňujte obslužné funkce událostí (`handleClick`, `handleChange`) před `useEffect`
* Nepoužívejte `React.memo()` — místo toho opravte kořenovou příčinu
* Omezte používání `useCallback` / `useMemo`
```tsx
// ❌ Bad — useEffect in the same component causes re-renders
export const Page = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <div>{data}</div>;
};
// ✅ Good — extract into sibling
export const PageData = () => {
const [data, setData] = useAtomState(dataState);
const [dep] = useAtomState(depState);
useEffect(() => { setData(dep); }, [dep]);
return <></>;
};
export const Page = () => {
const [data] = useAtomState(dataState);
return <div>{data}</div>;
};
```
## TypeScript
* **`type` místo `interface`** — flexibilnější, lépe kombinovatelný
* **Řetězcové literály místo výčtů** — s výjimkou enumů GraphQL codegenu a interních API knihoven
* **Žádné `any`** — vynucený přísný TypeScript
* **Žádné importy typů** — používejte běžné importy (vynuceno nástrojem Oxlint `typescript/consistent-type-imports`)
* **Používejte [Zod](https://github.com/colinhacks/zod)** pro runtime validaci netypovaných objektů
## JavaScript
```tsx
// Use nullish-coalescing (??) instead of ||
const value = process.env.MY_VALUE ?? 'default';
// Use optional chaining
onClick?.();
```
## Pojmenovávání
* **Proměnné**: camelCase, popisné (`email` nikoli `value`, `fieldMetadata` nikoli `fm`)
* **Konstanty**: SCREAMING_SNAKE_CASE
* **Typy/Třídy**: PascalCase
* **Soubory/adresáře**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`)
* **Obslužné funkce událostí**: `handleClick` (nikoli `onClick` pro obslužnou funkci)
* **Vlastnosti komponenty (props)**: předponu tvoří název komponenty (`ButtonProps`)
* **Stylované komponenty**: předpona `Styled` (`StyledTitle`)
## Styling
Používejte stylované komponenty [Linaria](https://github.com/callstack/linaria). Používejte hodnoty z tématu — vyhněte se napevno zadaným `px`, `rem` nebo barvám.
```tsx
// ❌ Bad
const StyledButton = styled.button`
color: #333333;
font-size: 1rem;
margin-left: 4px;
`;
// ✅ Good
const StyledButton = styled.button`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
margin-left: ${({ theme }) => theme.spacing(1)};
`;
```
## Importy
Používejte aliasy místo relativních cest:
```tsx
// ❌ Bad
import { Foo } from '../../../../../testing/decorators/Foo';
// ✅ Good
import { Foo } from '~/testing/decorators/Foo';
import { Bar } from '@/modules/bar/components/Bar';
```
## Struktura složek
```
front
└── modules/ # Feature modules
│ └── module1/
│ ├── components/
│ ├── constants/
│ ├── contexts/
│ ├── graphql/ (fragments, queries, mutations)
│ ├── hooks/
│ ├── states/ (atoms, selectors)
│ ├── types/
│ └── utils/
└── pages/ # Route-level components
└── ui/ # Reusable UI components (display, input, feedback, ...)
```
* Moduly mohou importovat z jiných modulů, ale `ui/` by mělo zůstat bez závislostí
* Používejte podadresáře `internal/` pro interní kód modulu
* Komponenty do 300 řádků, služby do 500 řádků

View File

@@ -0,0 +1,55 @@
---
title: API
icon: plug
description: REST a GraphQL API generovaná z vašeho schématu pracovního prostoru.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
## API schéma pro jednotlivé nájemce
Pro dvacet není žádný statický API odkaz. Každý pracovní prostor má své vlastní schéma — když přidáte vlastní objekt (řekněme `Invoice`), okamžitě získá REST a GraphQL koncové body shodné s vestavěnými objekty, jako je `Company` nebo `Person`. API je generováno z schématu, takže koncové body používají vaše názvy objektů a polí přímo – žádné neprůhledné ID.
Dokumentace API specifická pro pracovní prostor je dostupná v **Nastavení → API a Webhooks** po vytvoření API klíče. Obsahuje interaktivní hřiště, kde můžete uskutečňovat skutečné hovory proti svým datům.
## Dvě API
**Jádro API** — `/rest/` a `/graphql/`
CRUD v záznamech: Lidé, Společnosti, příležitosti, vaše vlastní objekty. Dotazy, filtry, pohybové vztahy.
**Metadata API** – `/rest/metadata/` a `/metadata/`
Správa schémat: vytvořit/upravit/odstranit objekty, pole a vztahy. Takto programově změníte svůj datový model.
Obojí je k dispozici jako REST a GraphQL. GraphQL přidává dávky a schopnost procházet vztahy v jediném dotazu. V každém případě stejné podkladové údaje.
## Základní URL adresy
| Prostředí | Základní URL |
| -------------------- | ------------------------- |
| Cloud | `https://api.twenty.com/` |
| Samostatně hostované | `https://{your-domain}/` |
## Ověření
```
Authorization: Bearer YOUR_API_KEY
```
Vytvořte API klíč v **Nastavení → API & Webhooks → + Vytvořit klíč**. Zkopírujte ji okamžitě - je to zobrazeno jednou. API klíče lze omezit na konkrétní roli v **Settings → Members → Roles → Assignment tab**, aby se omezilo, k čemu mají přístup.
<VimeoEmbed videoId="928786722" title="Vytvoření klíče API" />
Pro přístup založený na OAuthu (externí aplikace jednající jménem uživatelů), viz [OAuth](/l/cs/developers/extend/oauth).
## Provoz šarže
Podpora REST i GraphQL dávkování až 60 záznamů pro každou žádost – vytvořit, aktualizovat nebo smazat. GraphQL také podporuje dávkový náhled (create-or-update v jednom hovoru) pomocí plurálu jako je `CreateCompanies`.
## Míra omezení
| Limit | Hodnota |
| -------------- | -------------------- |
| Požadavky | 100 za minutu |
| Velikost šarže | 60 záznamů na volání |

View File

@@ -0,0 +1,124 @@
---
title: Konfigurace aplikace
description: Deklarujte identitu své aplikace, výchozí roli, proměnné a metadata tržiště pomocí defineApplication.
icon: rocket
---
Každá aplikace musí mít právě jedno volání `defineApplication`. Deklaruje:
* **Identita** — univerzální identifikátor, zobrazovaný název, popis.
* **Oprávnění** — pod jakou rolí běží její logické funkce a frontendové komponenty.
* **Proměnné** *(volitelné)* — páry klíč–hodnota zpřístupněné vašemu kódu jako proměnné prostředí.
* **Předinstalační / postinstalační / odinstalační hooky** *(volitelné)* — viz [Logické funkce](/l/cs/developers/extend/apps/logic/logic-functions).
```ts src/application-config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
displayName: 'My Twenty App',
description: 'My first Twenty app',
applicationVariables: {
DEFAULT_RECIPIENT_NAME: {
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
description: 'Default recipient name for postcards',
value: 'Jane Doe',
isSecret: false,
},
},
});
```
Poznámky:
* Pole `universalIdentifier` jsou deterministické identifikátory, které vlastníte. Vygenerujte je jednou a zachovejte je stabilní napříč synchronizacemi.
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce a frontendové komponenty. V logických funkcích (na straně serveru) jsou dostupné jako `process.env.VARIABLE_NAME`. Ve frontendových komponentách použijte `getApplicationVariable('VARIABLE_NAME')` z `twenty-sdk/front-component`. Proměnné označené jako `isSecret: true` jsou předávány pouze do logických funkcí. Frontendové komponenty přijímají pouze proměnné, které nejsou tajné.
* Výchozí role je automaticky detekována ze souboru role označeného pomocí [`defineApplicationRole()`](/l/cs/developers/extend/apps/config/roles) — není potřeba na ni odkazovat z `defineApplication()`.
* Předinstalační, postinstalační a odinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`.
* Předávání `defaultRoleUniversalIdentifier` explicitně je stále podporováno kvůli zpětné kompatibilitě, ale je zastaralé ve prospěch `defineApplicationRole()`.
* `serverVariables` představují konfiguraci a tajné údaje vázané na instanci (např. klíče API). Na rozdíl od `applicationVariables` neuvádějí v manifestu žádnou hodnotu — operátor pracovního prostoru je vyplní v nastavení aplikace a do logických funkcí jsou injektovány až poté, co jsou nastaveny.
* Oba druhy proměnných přijímají `isDeprecated: true`. Použijte ji k vyřazení proměnné místo jejího smazání: ponechání deklarovaného klíče zachová uloženou hodnotu (jejím smazáním se zničí hodnota, kterou zadal uživatel) a proměnná se stále injektuje, takže ji váš kód může použít jako záložní hodnotu — `process.env.NEW_API_KEY ?? process.env.API_KEY`. Zastaralá proměnná zmizí z nastavení aplikace, jakmile nemá žádnou hodnotu, a nikdy se nezapočítává do kontroly konfigurace aplikace, takže `isDeprecated` má přednost před `isRequired`.
* Chcete-li vykreslit vlastní konfigurační uživatelské rozhraní na kartě **Settings** aplikace (namísto výchozí sekce pro konfiguraci proměnných), deklarujte frontovou komponentu pomocí [`defineSettingsFrontComponent()`](/l/cs/developers/extend/apps/layout/front-components#custom-settings-component) v jejím vlastním souboru. Na jednu aplikaci je povolena pouze jedna instance. Sekce spravované systémem (automatická aktualizace, App URL, připojení) zůstávají vždy viditelné.
## Typy proměnných
Jak `applicationVariables`, tak `serverVariables` přijímají volitelný `type` (a pro `SELECT` / `MULTI_SELECT` i seznam `options`). Podporované typy: `TEXT` (výchozí), `BOOLEAN`, `NUMBER`, `NUMERIC`, `DATE`, `DATE_TIME`, `SELECT`, `MULTI_SELECT`, `ARRAY`, `RAW_JSON`, `RICH_TEXT`.
```ts src/application-config.ts
import { defineApplication, FieldType } from 'twenty-sdk/define';
export default defineApplication({
// ...identity, role...
applicationVariables: {
MAX_POSTCARDS: {
universalIdentifier: '5f4497e4-9030-4085-85eb-2c48b8d53713',
description: 'Maximum postcards per batch',
type: FieldType.NUMBER,
value: 10,
},
DEFAULT_REGION: {
universalIdentifier: '76c5c321-b6b6-46eb-b4fc-f9f04bb04227',
description: 'Default shipping region',
type: FieldType.SELECT,
options: [
{ label: 'Europe', value: 'eu' },
{ label: 'United States', value: 'us' },
],
value: 'eu',
},
},
});
```
`type` ovlivňuje pouze **prezentaci a validaci** — v uživatelském rozhraní nastavení pracovního prostoru vybere odpovídající vstup (přepínač, číselné pole, rozbalovací seznam, výběr data, editor JSON, …) a umožní sestavení ověřit vaši konfiguraci (například `SELECT` / `MULTI_SELECT` musí deklarovat neprázdné `options`). Nijak **nemění** způsob, jakým se hodnota dostane do vašeho kódu.
Hodnoty jsou **vždy předávány jako řetězce** — je to dáno povahou proměnných prostředí (`process.env.*` obsahuje pouze řetězce). Když se spustí vaše logická funkce, executor serializuje každou hodnotu podle jejího deklarovaného `type` při sestavování `process.env`, takže formát řetězce je konzistentní bez ohledu na to, jak byla hodnota nastavena (výchozí hodnota v manifestu, v uživatelském rozhraní nastavení nebo v předchozí verzi):
| Typ | řetězec `process.env` |
| ------------------------------------- | --------------------------------------- |
| `TEXT`, `SELECT`, `DATE`, `DATE_TIME` | surová hodnota (`"eu"`, `"2026-01-01"`) |
| `BOOLEAN` | `"true"` / `"false"` |
| `NUMBER`, `NUMERIC` | desetinný řetězec (`"10"`, `"2.5"`) |
| `MULTI_SELECT`, `ARRAY` | JSON pole (`'["email","postcard"]'`) |
| `RAW_JSON`, `RICH_TEXT` | JSON objekt (`'{"retries":3}'`) |
Parsujte řetězec zpět do typu, který očekáváte:
```ts
const maxCards = Number(process.env.MAX_POSTCARDS); // "10" -> 10
const enabled = process.env.ENABLE_TRACKING === 'true'; // "true" -> true
const channels = JSON.parse(process.env.ENABLED_CHANNELS ?? '[]'); // '["email"]' -> ["email"]
const config = JSON.parse(process.env.PROVIDER_CONFIG ?? '{}'); // '{"retries":3}' -> { retries: 3 }
```
Totéž platí pro frontendové komponenty, které čtou hodnoty pomocí `getApplicationVariable('VARIABLE_NAME')` — vrácená hodnota je řetězec; podle potřeby ji parsujte.
## Výchozí role funkce
Role deklarovaná pomocí [`defineApplicationRole()`](/l/cs/developers/extend/apps/config/roles) určuje, k čemu mají přístup logické funkce a front-endové komponenty aplikace:
* Běhové tokeny vložené do vašich logických funkcí jsou odvozeny z této role. Volání jednající jako osoba je dále omezeno tím, co tato osoba může dělat, takže nikdy nemůže překročit ani jedno z těchto oprávnění. Vizte [Čí přístup volání používá](/l/cs/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses).
* Typovaný klient API je omezen na oprávnění udělená této roli.
* Dodržujte princip nejmenších oprávnění: deklarujte pouze ta oprávnění, která vaše funkce potřebují.
Když vygenerujete novou aplikaci, CLI vytvoří úvodní soubor role v `src/roles/default-role.ts`. Úplnou referenci najdete v části [Role a oprávnění](/l/cs/developers/extend/apps/config/roles).
## Metadata tržiště
Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/operations/publishing), tato volitelná pole určují, jak se vaše aplikace zobrazuje v tržišti:
| Pole | Popis |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `author` | Jméno autora nebo název společnosti |
| `category` | Kategorie aplikace pro filtrování v tržišti |
| `logo` | Cesta k logu vaší aplikace uloženému ve složce `public/` (např. `public/logo.png`) |
| `galleryImages` | Pole cest ke snímkům galerie uloženým ve složce `public/` (např. `public/screenshot-1.png`) |
| `aboutDescription` | Delší popis v Markdownu pro kartu "O aplikaci". Pokud je vynecháno, tržiště použije `README.md` balíčku z npm |
| `websiteUrl` | Odkaz na váš web |
| `termsUrl` | Odkaz na podmínky služby |
| `emailSupport` | E-mailová adresa podpory |
| `issueReportUrl` | Odkaz na nástroj pro sledování problémů |
<Note>
`logoUrl` a `screenshots` jsou zastaralé aliasy `logo` a `galleryImages`. Externí absolutní adresy URL (`http://` nebo `https://`) nejsou pro tato pole podporovány: při sestavení jsou vynechány s varováním. Místo toho přiložte obrázky do složky `public/` vaší aplikace.
</Note>

View File

@@ -0,0 +1,188 @@
---
title: Instalační hooky
description: Spouštějte logiku během životního cyklu instalace, upgradu nebo odinstalace – vložte výchozí data, zálohujte záznamy, ověřte upgrade, vyčistěte externí prostředky.
icon: wrench
---
Instalační hooky jsou speciální logické funkce, které se spouštějí během životního cyklu instalace, upgradu nebo odinstalace. Sdílí stejný runtime handleru jako běžné [logické funkce](/l/cs/developers/extend/apps/logic/logic-functions), ale deklarují se pomocí vlastních definičních funkcí a fungují mimo běžný model triggerů (HTTP, cron, databázové události). Instalační hooky přijímají `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` je při čisté instalaci `undefined`); hook pro odinstalaci přijímá `UninstallPayload` (`{ version?: string }` — verze, která se odstraňuje).
Každá aplikace smí definovat **nanejvýš jeden** hook každého typu (předinstalační, poinstalační, odinstalační). Sestavení manifestu skončí chybou, pokud je zjištěn více než jeden hook daného typu.
```
┌─────────────────────────────────────────────────────────────┐
│ install flow │
│ │
│ upload package → [pre-install] → metadata migration → │
│ generate SDK → [post-install] │
│ │
│ old schema visible new schema visible │
└─────────────────────────────────────────────────────────────┘
```
## Na první pohled
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
| --------------- | --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Spuštění | Před migrací metadat — **předchozí** schéma a data jsou stále neporušené | Po migraci a vygenerování SDK — **nové** schéma je připravené |
| Spuštění | Vždy synchronní; blokuje instalaci | Ve výchozím nastavení asynchronní (zařazeno do fronty, 3 pokusy); synchronní režim volitelný přes `shouldRunSynchronously: true` |
| Při selhání | Instalace je **zrušena** před jakoukoli změnou schématu | Asynchronně: opakováno až 3krát. Synchronně: volající obdrží `POST_INSTALL_ERROR` (změny schématu se **ne**vracejí zpět) |
| Typické použití | Zálohovat nebo opravit data, která by migrace ztratila; odmítnout rizikovou aktualizaci vyvoláním výjimky | Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky |
**Pravidlo:** výchozí volbou je post-install. Po pre-install sáhněte pouze tehdy, když je samotná migrace destruktivní a potřebujete zachytit předchozí stav, než zmizí.
| Chcete... | Použít |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Naplňte data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` |
| Dlouho běžící práce, která by neměla blokovat odpověď instalace | `post-install` (výchozí asynchronní režim, s opakovanými pokusy workeru) |
| Rychlé nastavení, na které volající spoléhá ihned po dokončení instalace | `post-install` s `shouldRunSynchronously: true` |
| Čtěte nebo zálohujte data, která by nadcházející migrace ztratila | `pre-install` |
| Odmítněte aktualizaci, která by poškodila existující data | `pre-install` (vyhoďte výjimku z obslužné funkce) |
| Srovnání stavu při každé aktualizaci | Libovolný hook s `shouldRunOnVersionUpgrade: true` |
## Chování sdílené oběma hooky
* Konfigurace je konfigurace `defineLogicFunction` bez nastavení triggeru, doplněná o `shouldRunOnVersionUpgrade`.
* **Kdy se spouští**: ve výchozím nastavení pouze při čistých instalacích. Nastavte `shouldRunOnVersionUpgrade: true`, aby se spouštěl i při aktualizacích. Použijte `previousVersion` / `newVersion` k větvení podle cesty aktualizace.
* **Idempotence je důležitá**: asynchronní post-install se může spouštět opakovaně a kterýkoli hook se znovu spouští při aktualizacích, když je `shouldRunOnVersionUpgrade` zapnuté.
* Běžné prostředí logické funkce (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) je injektováno, takže můžete volat Twenty API s tokenem své aplikace.
* Hook je při sestavení automaticky připojen k manifestu aplikace (`preInstallLogicFunction` / `postInstallLogicFunction`) — v [`defineApplication()`](/l/cs/developers/extend/apps/config/application) není potřeba na nic odkazovat.
* Výchozí `timeoutSeconds` je 300, aby umožnil delší úlohy nastavení, jako je naplnění daty.
* **Nespouští se v dev režimu**: `yarn twenty dev` přeskočí instalační flow a soubory synchronizuje přímo, takže se hooky v tomto režimu nikdy nespustí. Místo toho je spouštějte ručně:
```bash filename="Terminal"
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
```
<AccordionGroup>
<Accordion title="definePostInstallLogicFunction" description="Spouští se po aplikování migrace metadat pracovního prostoru">
Spouští se, jakmile vaše aplikace dokončí instalaci: metadata jsou synchronizovaná, klient SDK vygenerovaný a nové schéma je možné dotazovat. Příklad — při čisté instalaci naplňte výchozí záznam:
```ts src/logic-functions/post-install.ts
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
if (previousVersion) return; // fresh installs only
const client = new CoreApiClient();
await client.mutation({
createPostCard: {
__args: { data: { name: 'Welcome to Postcard', content: 'Your first card!' } },
id: true,
},
});
};
export default definePostInstallLogicFunction({
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
name: 'post-install',
description: 'Seeds a welcome post card after install.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: false,
shouldRunSynchronously: false,
handler,
});
```
Příznak `shouldRunSynchronously` řídí model spuštění:
* `false` *(výchozí)* — zařazeno do fronty zpráv (`retryLimit: 3`) a spuštěno workerem. Odezva instalace se vrátí, jakmile je úloha zařazena do fronty. **Používejte pro dlouho běžící práci** — naplňování velkých datových sad, pomalá API třetích stran.
* `true` — spuštěno inline během instalačního flow. Instalační požadavek blokuje, dokud handler neskončí; vyvolaná chyba se projeví pro volajícího jako `POST_INSTALL_ERROR` (žádné opakované pokusy). **Používejte pro rychlou práci, která musí být dokončena před odpovědí.** Migrace už je v tomto bodě aplikovaná, takže selhání nevrací změny schématu zpět — pouze předá chybu dál.
</Accordion>
<Accordion title="definePreInstallLogicFunction" description="Spouští se před aplikováním migrace metadat pracovního prostoru">
Spouští se před migrací metadat, nad **předchozím** schématem — vhodné místo pro zálohování dat, která by migrace ztratila, nebo pro odmítnutí rizikové aktualizace. Před spuštěním server provede čistě aditivní „zjednodušenou synchronizaci“, která zaregistruje pouze pre-install funkci nové verze; vše ostatní — objekty, pole a data předchozí verze — zůstává při běhu vašeho handleru nedotčeno.
Pre-install je vždy **synchronní** a blokuje instalaci. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před jakoukoli změnou schématu — pracovní prostor zůstane na předchozí verzi v konzistentním stavu. Je to záměrné: pre-install je vaše poslední šance odmítnout rizikovou aktualizaci.
Příklad — zkopírujte hodnoty staršího pole dříve, než ho migrace odstraní:
```ts src/logic-functions/pre-install.ts
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async ({ previousVersion, newVersion }: InstallPayload): Promise<void> => {
// Only the 1.x → 2.x upgrade drops the legacy `notes` field.
if (!previousVersion?.startsWith('1.') || !newVersion.startsWith('2.')) {
return;
}
const client = new CoreApiClient();
const { postCards } = await client.query({
postCards: {
__args: { filter: { notes: { isNot: null } } },
edges: { node: { id: true, notes: true } },
},
});
// Copy legacy `notes` into `description` before the migration drops the
// column. If this fails, the upgrade aborts and the workspace stays on v1.
for (const { node } of postCards.edges) {
await client.mutation({
updatePostCard: {
__args: { id: node.id, data: { description: node.notes } },
id: true,
},
});
}
};
export default definePreInstallLogicFunction({
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
name: 'pre-install',
description: 'Backs up legacy notes into description before the v2 migration.',
timeoutSeconds: 300,
shouldRunOnVersionUpgrade: true,
handler,
});
```
</Accordion>
</AccordionGroup>
## Odinstalační hook
`defineUninstallLogicFunction` deklaruje hook, který se spustí, když uživatel odinstaluje vaši aplikaci. Spustí se **předtím**, než jsou odstraněna metadata, data a kód aplikace — jakmile se spustí migrační skript pro smazání, nezbude nic, co by se dalo vykonat — takže váš handler může stále dotazovat objekty a záznamy aplikace. Použijte ho k úklidu externích prostředků: zrušení přidělených API prostředků, smazání zbývajících botů, zrušení webhooků.
Poznámky:
* Hook pracuje v režimu best effort: běží synchronně, ale chyba se pouze zapíše do logu a nikdy neblokuje odinstalaci — úklid nesmí znemožnit odebrání aplikace.
* Přijímá `UninstallPayload` (`{ version?: string }` — verze, která se odstraňuje).
* Nespouští se, pokud je neúspěšná čistá instalace vrácena zpět — aplikace nikdy nedokončila instalaci.
* Hook nemůže běžet poté, co je aplikace pryč, takže externí úklid, který závisí na datech aplikace (např. ID botů uložená v záznamech), patří sem, ne do externí naplánované úlohy.
* Stejně jako instalační hooky se **nespouští v dev módu** — místo toho ho vyvolejte ručně:
```bash filename="Terminal"
yarn twenty dev:function:exec --uninstall
```
```ts src/logic-functions/uninstall.ts
import { defineUninstallLogicFunction, type UninstallPayload } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (_payload: UninstallPayload): Promise<void> => {
const client = new CoreApiClient();
const { meetingBots } = await client.query({
meetingBots: { edges: { node: { id: true, externalBotId: true } } },
});
// Delete the provider-side bots so nothing keeps recording after uninstall.
for (const { node } of meetingBots.edges) {
await fetch(`https://api.recorder.example/bots/${node.externalBotId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.RECORDER_API_KEY}` },
});
}
};
export default defineUninstallLogicFunction({
universalIdentifier: 'b2c3d4e5-6789-01bc-def0-234567890abc',
name: 'uninstall',
description: 'Deletes remaining recorder bots when the app is uninstalled.',
timeoutSeconds: 300,
handler,
});
```

View File

@@ -0,0 +1,56 @@
---
title: Přehled
description: Nakonfigurujte samotnou aplikaci – její identitu, výchozí oprávnění a to, co se spouští při instalaci.
icon: screwdriver-wrench
---
**Konfigurační vrstva** aplikace Twenty popisuje aplikaci *platformě* – její identitu, oprávnění, která má, a kód, který se spouští během instalace nebo aktualizace. Tato deklarativní nastavení nepřidávají nové datové struktury ani chování za běhu; říkají Twenty, *co je aplikace zač* a *jak ji nastavit*.
```text
┌────────────────────────────────────────────────────────┐
│ Application — identity, default role, variables, │
│ marketplace metadata │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Role — what the app's logic functions can read │ │
│ │ and write (referenced by Application) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼ (at install / upgrade time)
┌──────────────────────────────────┐
│ Pre-install hook │ before metadata migration
└──────────────────────────────────┘
┌──────────────────────────────────┐
│ Post-install hook │ after metadata migration
└──────────────────────────────────┘
│
▼ (at uninstall time)
┌──────────────────────────────────┐
│ Uninstall hook │ before app removal
└──────────────────────────────────┘
```
## V této části
<CardGroup cols={2}>
<Card title="Konfigurace aplikace" icon="rocket" href="/l/cs/developers/extend/apps/config/application">
`defineApplication` – identita, výchozí role, proměnné, metadata pro marketplace.
</Card>
<Card title="Role a oprávnění" icon="shield-halved" href="/l/cs/developers/extend/apps/config/roles">
`defineRole` – deklaruje, co mohou logické funkce vaší aplikace číst a zapisovat.
</Card>
<Card title="Instalační hooky" icon="wrench" href="/l/cs/developers/extend/apps/config/install-hooks">
`definePreInstallLogicFunction`, `definePostInstallLogicFunction` a `defineUninstallLogicFunction` — zálohují data, nastavují výchozí hodnoty, validují aktualizace, uklízejí při odebrání.
</Card>
</CardGroup>
## Jak spolu části souvisejí
* **Aplikace** je vstupní bod. Každá aplikace má právě jedno volání `defineApplication()`, které ukazuje na jednu **roli** jako výchozí.
* **Role** určuje, co mohou logické funkce aplikace a front-endové komponenty číst a zapisovat. Dodržujte zásadu nejmenších oprávnění: udělujte pouze ta oprávnění, která váš kód skutečně potřebuje.
* **Instalační hooky** se spouštějí během instalace nebo aktualizace – pre-install před migrací metadat (aby mohly odmítnout rizikovou aktualizaci), post-install po migraci (aby mohly proti novému schématu naplnit výchozí data). Hook pro odinstalaci se spouští těsně před odebráním aplikace, takže může uklidit externí prostředky, zatímco data aplikace jsou stále čitelná.
<Note>
Instalační hooky sdílejí běhové prostředí [logických funkcí](/l/cs/developers/extend/apps/logic/logic-functions) – stejný podpis handleru, stejné proměnné prostředí, stejný typovaný klient API – ale deklarují se pomocí vlastních funkcí define a fungují mimo běžný model spouštěčů (HTTP, cron, databázové události).
</Note>

View File

@@ -0,0 +1,67 @@
---
title: Veřejné soubory
description: Doručujte statické soubory — obrázky, ikony, písma — spolu se svou aplikací prostřednictvím složky public/.
icon: folder-open
---
Složka `public/` v kořenu vaší aplikace obsahuje statické soubory — obrázky, ikony, písma a další prostředky, které vaše aplikace potřebuje za běhu. Tyto soubory jsou automaticky zahrnuty do buildů, synchronizovány během vývojového režimu a nahrávány na server.
Soubory umístěné v `public/` jsou:
* **Veřejně přístupné** — po synchronizaci na server jsou prostředky dostupné na veřejné URL. K přístupu k nim není potřeba žádná autentizace.
* **Dostupné ve frontendových komponentách** — použijte URL prostředků k zobrazení obrázků, ikon či jiných médií uvnitř komponent Reactu.
* **Dostupné v logických funkcích** — odkazujte na URL prostředků v e-mailech, odpovědích API či jiné serverové logice.
* **Používány pro metadata Marketplace** — pole `logo` a `galleryImages` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Zobrazují se v Marketplace, když je vaše aplikace zveřejněna. Pro tato pole jsou externí absolutní adresy URL ignorovány — místo toho přibalte obrázky do `public/`. Každý obrázek nesmí přesáhnout velikost 10 MB.
* **Automaticky synchronizované ve vývojovém režimu** — když v `public/` přidáte, aktualizujete nebo smažete soubor, je automaticky synchronizován na server. Není potřeba restartovat.
* **Zahrnuté do buildů** — `yarn twenty dev:build` zabalí všechny veřejné prostředky do distribučního výstupu.
## Přístup k veřejným prostředkům pomocí `getPublicAssetUrl`
K získání plné URL souboru ve vaší složce `public/` použijte pomocnou funkci `getPublicAssetUrl` z `twenty-sdk`. Funguje jak v logických funkcích, tak ve frontendových komponentách.
**V logické funkci:**
```ts src/logic-functions/send-invoice.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const handler = async (): Promise<any> => {
const logoUrl = getPublicAssetUrl('logo.png');
const invoiceUrl = getPublicAssetUrl('templates/invoice.png');
// Fetch the file content (no auth required — public endpoint)
const response = await fetch(invoiceUrl);
const buffer = await response.arrayBuffer();
return { logoUrl, size: buffer.byteLength };
};
export default defineLogicFunction({
universalIdentifier: 'a1b2c3d4-...',
name: 'send-invoice',
description: 'Sends an invoice with the app logo',
timeoutSeconds: 10,
handler,
});
```
**Ve frontendové komponentě:**
```tsx src/front-components/company-card.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const CompanyCard = () => {
const logoUrl = getPublicAssetUrl('logo.png');
return <img src={logoUrl} alt="App logo" />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'company-card',
component: CompanyCard,
});
```
Argument `path` je relativní ke složce `public/` vaší aplikace. Jak `getPublicAssetUrl('logo.png')`, tak `getPublicAssetUrl('public/logo.png')` se vyhodnotí na stejnou URL — předpona `public/` je, je-li přítomna, automaticky odstraněna.

View File

@@ -0,0 +1,201 @@
---
title: Role a oprávnění
description: Určete, které objekty a pole mohou funkce aplikační logiky a front-endové komponenty vaší aplikace číst a zapisovat.
icon: shield-halved
---
**Role** je sada oprávnění: které objekty může aplikace číst nebo zapisovat, která pole může vidět a jaké schopnosti na úrovni platformy může používat. Všechny logické funkce aplikace a front-endové komponenty dědí oprávnění role označené pomocí `defineApplicationRole()` (viz [Výchozí role funkce](#the-default-function-role) níže).
```ts src/roles/restricted-company-role.ts
import {
defineRole,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
SystemPermissionFlag,
} from 'twenty-sdk/define';
export default defineRole({
universalIdentifier: '2c80f640-2083-4803-bb49-003e38279de6',
label: 'My new role',
description: 'A role that can be used in your workspace',
canReadAllObjectRecords: false,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
canSoftDeleteObjectRecords: false,
canDestroyObjectRecords: false,
},
],
fieldPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name
.universalIdentifier,
canReadFieldValue: false,
canUpdateFieldValue: false,
},
],
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.APPLICATIONS],
});
```
## Zabezpečení na úrovni řádků
Oprávnění objektů a polí určují, *kterých objektů a polí* se může role dotýkat. **Predikáty oprávnění na úrovni řádků** jdou dále a určují, *které záznamy* může role vidět a s nimi pracovat — například samoobslužná role, kde každý externí uživatel vidí pouze své vlastní záznamy.
Predikáty deklarujte na roli pomocí `rowLevelPermissionPredicates`. Stejně jako zbytek manifestu nese každý predikát svůj vlastní `universalIdentifier` a odkazuje na objekt a pole podle jejich `universalIdentifier`, dále na `operand` a (volitelně) na pole workspaceMember, jehož hodnota je vložena v době dotazu — takže můžete vyjádřit „vztah vlastníka záznamu **je** aktuálním členem pracovního prostoru“:
```ts src/roles/partner-role.ts
import {
defineRole,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/account-owner.field';
export default defineRole({
universalIdentifier: 'c3c1dc2e-1a08-4de5-abb7-2139b3d99343',
label: 'Partner',
description: 'External partner — sees only its own records',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
canReadObjectRecords: true,
canUpdateObjectRecords: true,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd0f0c1a2-3b4c-4d5e-8f60-111111111111',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
fieldUniversalIdentifier: ACCOUNT_OWNER_FIELD_UNIVERSAL_IDENTIFIER,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier,
},
],
});
```
Protože predikáty jsou součástí manifestu, vytvářejí se, aktualizují a odstraňují společně s rolí při každé instalaci a aktualizaci — není potřeba žádný samostatný post‑instalační krok, který by je udržoval v synchronizaci.
### Kombinování predikátů se skupinami
Ve výchozím nastavení se predikáty role kombinují pomocí `AND`. Aby bylo možné některé z nich kombinovat pomocí `OR` (nebo vnořovat logiku), deklarujte položku `rowLevelPermissionPredicateGroups` a každý predikát na ni odkažte prostřednictvím `predicateGroupUniversalIdentifier`. Tato role umožňuje partnerovi zobrazit obchodní příležitost, kterou buď vlastní, nebo u ní vystupuje jako kontaktní osoba:
```ts src/roles/partner-opportunities-role.ts
import {
defineRole,
RowLevelPermissionPredicateGroupLogicalOperator,
RowLevelPermissionPredicateOperand,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
const OPPORTUNITY = STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity;
const CURRENT_MEMBER =
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.fields.id
.universalIdentifier;
export default defineRole({
universalIdentifier: 'b2a1c0d9-8e7f-4a6b-9c5d-222222222222',
label: 'Partner (opportunities)',
canBeAssignedToUsers: true,
objectPermissions: [
{
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
canReadObjectRecords: true,
},
],
rowLevelPermissionPredicateGroups: [
{
universalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.OR,
},
],
rowLevelPermissionPredicates: [
{
universalIdentifier: 'd4c3b2a1-0e9f-4c8b-9e7d-444444444444',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier: OPPORTUNITY.fields.owner.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
{
universalIdentifier: 'e5d4c3b2-1f0e-4d9c-8f8e-555555555555',
objectUniversalIdentifier: OPPORTUNITY.universalIdentifier,
fieldUniversalIdentifier:
OPPORTUNITY.fields.pointOfContact.universalIdentifier,
operand: RowLevelPermissionPredicateOperand.IS,
workspaceMemberFieldUniversalIdentifier: CURRENT_MEMBER,
predicateGroupUniversalIdentifier: 'c3b2a1d0-9f8e-4b7a-8d6c-333333333333',
},
],
});
```
Poznámky:
* Každému predikátu a skupině dejte stabilní `universalIdentifier` (libovolný uuid) — podle něj se entita identifikuje při aktualizacích a predikáty podle něj odkazují na skupiny.
* Predikáty mohou odkazovat na objekty a pole vlastněné vaší aplikací nebo na standardní objekty Twenty.
* Zabezpečení na úrovni řádků je vynucováno pro pracovní prostory v tarifech, které ho zahrnují; predikáty se i na ostatních tarifech stále synchronizují, pouze nejsou vynucovány.
## Výchozí role funkce
Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role deklarovaný pomocí `defineApplicationRole()`:
```ts src/roles/default-role.ts
import { defineApplicationRole } from 'twenty-sdk/define';
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
'b648f87b-1d26-4961-b974-0908fd991061';
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Default function role',
description: 'Default role for function Twenty client',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: false,
canSoftDeleteAllObjectRecords: false,
canDestroyAllObjectRecords: false,
canUpdateAllSettings: false,
canBeAssignedToAgents: false,
canBeAssignedToUsers: false,
canBeAssignedToApiKeys: false,
objectPermissions: [],
fieldPermissions: [],
permissionFlagUniversalIdentifiers: [],
});
```
`defineApplicationRole()` je tenký wrapper kolem `defineRole()`, který označuje **tu** roli, jež je při instalaci použita jako výchozí role vaší aplikace. Validace je shodná s `defineRole`, ale build pipeline automaticky propojí její `universalIdentifier` s `defaultRoleUniversalIdentifier` v manifestu aplikace — takže na něj nemusíte v [`defineApplication`](/l/cs/developers/extend/apps/config/application) sami odkazovat.
Poznámky:
* Na jednu aplikaci je povolena přesně **jedna** definice `defineApplicationRole(...)` — pokud build manifestu najde více než jednu, sestavení selže.
* Pro všechny **další** role, které vaše aplikace poskytuje, použijte `defineRole()` (nikoli `defineApplicationRole()`).
* Explicitní nastavení `defaultRoleUniversalIdentifier` v `defineApplication()` je stále podporováno kvůli zpětné kompatibilitě, ale je označeno jako zastaralé ve prospěch `defineApplicationRole()`.
## Osvědčené postupy
* Začněte od vygenerované role a postupně ji omezujte — výchozí nastavení poskytuje široká oprávnění pro čtení, což je v produkci zřídka žádoucí.
* Nahraďte `objectPermissions` a `fieldPermissions` přesně těmi objekty a poli, které vaše funkce skutečně potřebují.
* `permissionFlagUniversalIdentifiers` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší.
* Podívejte se na funkční příklad: [`examples/hello-world/src/roles/default-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/hello-world/src/roles/default-role.ts).

View File

@@ -0,0 +1,52 @@
---
title: Rozšiřování objektů
description: Přidávejte pole ke standardním objektům Twenty (Person, Company, …) nebo k objektům z jiných aplikací pomocí `defineField`.
icon: wand-magic-sparkles
---
Použijte `defineField()` pro přidání pole k objektu, který nevlastníte — standardnímu objektu Twenty, jako je Person nebo Company, nebo objektu dodanému jinou nainstalovanou aplikací. Na rozdíl od inline polí deklarovaných uvnitř [`defineObject`](/l/cs/developers/extend/apps/data/objects) vyžadují samostatná pole `objectUniversalIdentifier` k určení, který objekt rozšiřují.
```ts src/fields/company-loyalty-tier.field.ts
import { defineField, FieldType } from 'twenty-sdk/define';
export default defineField({
universalIdentifier: 'f2a1b3c4-d5e6-7890-abcd-ef1234567890',
objectUniversalIdentifier: '701aecb9-eb1c-4d84-9d94-b954b231b64b', // Company object
name: 'loyaltyTier',
type: FieldType.SELECT,
label: 'Loyalty Tier',
icon: 'IconStar',
options: [
{ value: 'BRONZE', label: 'Bronze', position: 0, color: 'orange' },
{ value: 'SILVER', label: 'Silver', position: 1, color: 'gray' },
{ value: 'GOLD', label: 'Gold', position: 2, color: 'yellow' },
],
});
```
## Hlavní body
* `objectUniversalIdentifier` identifikuje cílový objekt. Pro standardní objekty Twenty importujte konstantu z `twenty-sdk`:
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier
// …
```
* Při definování polí **inline uvnitř `defineObject()`** `objectUniversalIdentifier` **nepotřebujete** — dědí se z nadřazeného objektu.
* `defineField()` je jediný způsob, jak přidat pole k objektům, které jste nevytvořili pomocí `defineObject()`.
* Umístění souboru je na vás. Konvence je `src/fields/\<name>.field.ts`, ale SDK rozpozná pole kdekoli v `src/`.
* `writability` lze také nastavit pro jednotlivá pole: `MetadataWritability.APPLICATION` omezuje zápisy na logické funkce vaší vlastní aplikace, `MetadataWritability.SYSTEM` je vyhrazeno pro data spravovaná platformou. Zapisovatelnost pole může být pouze přísnější než úroveň jeho objektu a čtení tím nejsou ovlivněna. Úplný popis sémantiky najdete v [Objects](/l/cs/developers/extend/apps/data/objects).
* Chcete-li přidat kartu ke standardnímu rozvržení stránky (např. na detailní stránku Task nebo Company), použijte [`definePageLayoutTab`](/l/cs/developers/extend/apps/layout/page-layouts#definepagelayouttab) s `STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS` z `twenty-sdk/define`.
## Přidání relace k existujícímu objektu
Chcete-li přidat relační pole (např. pro propojení vlastního objektu se standardním `Person`), použijte `defineField()` s `FieldType.RELATION`. Vzor je stejný jako u inline relací, ale s `objectUniversalIdentifier` nastaveným explicitně. Obousměrný vzor najdete v části [Relations](/l/cs/developers/extend/apps/data/relations).

View File

@@ -0,0 +1,143 @@
---
title: Objekty
description: Deklarujte nové typy záznamů – vlastní tabulky s jejich vlastními poli – pomocí defineObject.
icon: table
---
Vlastní **objekty** jsou nové typy záznamů, které vaše aplikace přidává do pracovního prostoru — pohlednice, faktura, předplatné, cokoli specifického pro vaši doménu. Každý objekt definuje své schéma (pole, vztahy, výchozí hodnoty) a stabilní univerzální identifikátor, který přetrvá mezi synchronizacemi a nasazeními.
```ts src/objects/post-card.object.ts
import { defineObject, FieldType } from 'twenty-sdk/define';
enum PostCardStatus {
DRAFT = 'DRAFT',
SENT = 'SENT',
DELIVERED = 'DELIVERED',
RETURNED = 'RETURNED',
}
export default defineObject({
universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05',
nameSingular: 'postCard',
namePlural: 'postCards',
labelSingular: 'Post Card',
labelPlural: 'Post Cards',
description: 'A post card object',
icon: 'IconMail',
fields: [
{
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
name: 'content',
type: FieldType.TEXT,
label: 'Content',
description: "Postcard's content",
icon: 'IconAbc',
},
{
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
name: 'recipientName',
type: FieldType.FULL_NAME,
label: 'Recipient name',
icon: 'IconUser',
},
{
universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266',
name: 'recipientAddress',
type: FieldType.ADDRESS,
label: 'Recipient address',
icon: 'IconHome',
},
{
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
name: 'status',
type: FieldType.SELECT,
label: 'Status',
icon: 'IconSend',
defaultValue: `'${PostCardStatus.DRAFT}'`,
options: [
{ value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' },
{ value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' },
{ value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' },
{ value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' },
],
},
{
universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433',
name: 'deliveredAt',
type: FieldType.DATE_TIME,
label: 'Delivered at',
icon: 'IconCheck',
isNullable: true,
defaultValue: null,
},
],
});
```
## Hlavní body
* Hodnota `universalIdentifier` musí být jedinečná a stabilní napříč nasazeními.
* Každé pole vyžaduje `name`, `type`, `label` a svůj vlastní stabilní `universalIdentifier`.
* Pole `fields` je volitelné — objekty můžete definovat i bez vlastních polí.
* `openRecordIn` určuje, kde se záznamy tohoto objektu otevřou po kliknutí: `ObjectOpenRecordIn.USER_CHOICE` (výchozí nastavení, které respektuje individuální předvolbu každého člena pracovního prostoru v Settings → Experience), `ObjectOpenRecordIn.SIDE_PANEL` nebo `ObjectOpenRecordIn.RECORD_PAGE`. Připněte jej k `RECORD_PAGE` pro záznamy, které ke svému používání potřebují celou stránku, stejně jako pracovní postupy a řídicí panely, nebo k `SIDE_PANEL` pro záznamy, které dávají smysl jen jako rychlý panel, podobně jako události v kalendáři.
* `writability` určuje, kdo vůbec smí zapisovat záznamy objektu, ještě předtím, než se uplatní oprávnění rolí: `MetadataWritability.OPEN` (výchozí – rozhodují role ve workspace), `MetadataWritability.APPLICATION` (záznamy mohou vytvářet, aktualizovat nebo mazat pouze logické funkce samotné aplikace; použijte pro objekty podobné konfiguraci, jejichž záznamy určují chování, aby je členové workspace s širokým přístupem k záznamům nemohli upravovat přes API) nebo `MetadataWritability.SYSTEM` (vyhrazeno pro data spravovaná platformou). Čtení nejsou ovlivněna – toto je vynucováno na straně serveru, na rozdíl od `isUIEditable`, které pouze skrývá prvky uživatelského rozhraní. Existuje také na úrovni jednotlivých polí, kde může být pouze přísnější než na úrovni objektu.
* Pole definovaná zde inline **nepotřebují** `objectUniversalIdentifier` — dědí se z nadřazeného objektu. Pomocí [`defineField()`](/l/cs/developers/extend/apps/data/extending-objects) můžete přidávat pole k objektům, které nevlastníte.
* Nové objekty můžete vygenerovat pomocí `yarn twenty dev:add object`, který vás provede pojmenováním, poli a vztahy. Viz [Architektura → Scaffolding entit](/l/cs/developers/extend/apps/getting-started/scaffolding).
<Note>
**Základní pole jsou přidána automaticky.** Když definujete vlastní objekt, Twenty pro vás vytvoří standardní pole jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`. Nemusíte je uvádět v poli `fields` — pouze svá vlastní pole. Výchozí pole můžete přepsat tak, že deklarujete pole se stejným názvem, ale jen zřídka je to dobrý nápad.
</Note>
## Typy polí
Úplná sada hodnot `FieldType`, exportovaných z `twenty-sdk/define`:
| Kategorie | Typy |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Text | `TEXT`, `RICH_TEXT`, `ARRAY` (řetězců), `RAW_JSON` |
| Číselné | `NUMBER` (`universalSettings.dataType`: `'float'` / `'int'` / `'bigint'`), `NUMERIC` (libovolná přesnost), `RATING`, `POSITION` |
| Datumy | `DATE`, `DATE_TIME` |
| Výběr | `BOOLEAN`, `SELECT`, `MULTI_SELECT` |
| Složené | `FULL_NAME`, `ADDRESS`, `EMAILS`, `PHONES`, `LINKS`, `CURRENCY`, `ACTOR`, `FILES` |
| Identifikátory a relace | `UUID`, `RELATION`, `MORPH_RELATION` (viz [Relations](/l/cs/developers/extend/apps/data/relations)) |
| Systém | `TS_VECTOR` (vektor pro fulltextové vyhledávání, spravovaný serverem) |
Složené typy ukládají více podpolí (např. `FULL_NAME` = křestní jméno + příjmení; `CURRENCY` = `amountMicros` + `currencyCode`). `SELECT` a `MULTI_SELECT` vyžadují pole `options`, jak je ukázáno v příkladu výše.
## Výchozí hodnoty
Výchozí textové hodnoty musí být uzavřené v jednoduchých uvozovkách **uvnitř** řetězce — `defaultValue: "'Draft'"`, ne `defaultValue: "Draft"`. Proto pole `status` výše používá `` `'${PostCardStatus.DRAFT}'` ``.
Neuzavřené (necitované) řetězce jsou vyhrazené pro vypočítané výchozí hodnoty, které se vyhodnocují při vytvoření záznamu:
* `'uuid'` — generuje UUID (pro pole `UUID`)
* `'now'` — aktuální časové razítko (pro pole `DATE_TIME`)
Stejná konvence platí pro řetězcová podpola složených výchozích hodnot (např. `{ source: "'MANUAL'" }` u pole `ACTOR`) a pro hodnoty `SELECT`/`MULTI_SELECT`. Doslovná řetězcová výchozí hodnota ponechaná bez uvozovek vyvolá při sestavení aplikace varování.
## Možnost hodnoty NULL
`isNullable` určuje, zda pole přijímá `NULL`. Výchozí hodnota je `true` — pro volitelná pole ji můžete vynechat. Nastavte `isNullable: false`, aby bylo pole vyžadováno na úrovni databáze.
Změny `isNullable` se použijí při každé synchronizaci, včetně těch, které aktualizují existující pole — takže můžete změnit, zda pole přijímá hodnotu NULL, úpravou manifestu a opětovnou synchronizací.
<Note>
**Změna existujícího pole tak, aby neumožňovalo hodnotu NULL, vyžaduje výchozí hodnotu.** Když změníte pole na `isNullable: false`, musíte také zadat nenulovou hodnotu `defaultValue`. Výchozí hodnota doplní všechny existující řádky s hodnotou `NULL` ještě předtím, než se uplatní omezení `NOT NULL`; bez ní synchronizace selže s chybou `Default value cannot be null for non-nullable fields`. Relační pole a pole typu `TS_VECTOR` vždy umožňují hodnotu NULL, takže na ně `isNullable` nemá žádný efekt.
</Note>
```ts
{
universalIdentifier: 'b1a7c0de-1234-4f00-9abc-000000000000',
name: 'reference',
type: FieldType.TEXT,
label: 'Reference',
isNullable: false,
defaultValue: "'N/A'",
}
```
## Co dál
* **Propojte tento objekt s ostatními** — vzor obousměrných vztahů najdete v části [Relations](/l/cs/developers/extend/apps/data/relations).
* **Přidávejte pole k objektům z jiných aplikací** — viz [Extending Objects](/l/cs/developers/extend/apps/data/extending-objects) pro `defineField()`.
* **Zobrazit tento objekt v uživatelském rozhraní** — viz [Položky navigační nabídky](/l/cs/developers/extend/apps/layout/navigation-menu-items) pro přidání položky do postranního panelu; viz [Zobrazení](/l/cs/developers/extend/apps/layout/views) pro přidání vlastních konfigurací seznamu.

View File

@@ -0,0 +1,101 @@
---
title: Přehled
description: Formujte data, která vaše aplikace přidává do pracovního prostoru — objekty, pole a vztahy.
icon: database
---
**Datová vrstva** aplikace Twenty představuje data, která vaše aplikace *přidává* do pracovního prostoru — nové typy záznamů, které deklaruje, sloupce, které přidává k existujícím objektům a způsob, jakým se tyto záznamy vzájemně propojují.
```text
┌──────────────────────────────────────────────────┐
│ Object — a record type, e.g. PostCard │
│ ├─ Field (name, type, label) │
│ ├─ Field │
│ └─ Relation (link to another object) │
└──────────────────────────────────────────────────┘
│
├── lives in your app, OR
│
▼
┌──────────────────────────────────────────────────┐
│ Standard / other apps' objects │
│ └─ Field added by your app via defineField │
└──────────────────────────────────────────────────┘
```
## V této části
<CardGroup cols={2}>
<Card title="Objekty" icon="table" href="/l/cs/developers/extend/apps/data/objects">
`defineObject` — deklarujte nové typy záznamů s jejich vlastními poli.
</Card>
<Card title="Rozšiřování objektů" icon="wand-magic-sparkles" href="/l/cs/developers/extend/apps/data/extending-objects">
`defineField` — přidejte pole ke standardním objektům nebo objektům jiných aplikací.
</Card>
<Card title="Vztahy" icon="diagram-project" href="/l/cs/developers/extend/apps/data/relations">
Obousměrná `MANY_TO_ONE` / `ONE_TO_MANY` propojení mezi objekty.
</Card>
<Card title="Typy aktivit na časové ose" icon="clock-rotate-left" href="/l/cs/developers/extend/apps/data/timeline-activity-types">
Definujte automatické události auditu a explicitní události aplikace pro časové osy záznamů.
</Card>
</CardGroup>
## Entity v kostce
| Entita | Účel | Definováno pomocí |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| **Objekt** | Nový vlastní typ záznamu (např. PostCard, Invoice) s vlastními poli | `defineObject()` |
| **Pole** | Sloupec v objektu. Samostatná pole mohou rozšiřovat objekty, které jste nevytvořili (např. přidat `loyaltyTier` k objektu Company) | `defineField()` |
| **Vztah** | Obousměrné propojení mezi dvěma objekty — obě strany deklarované jako pole | `defineField()` s `FieldType.RELATION` |
| **Index** | Databázový index pro zrychlení opakovaného dotazu na jeden z vašich objektů | `defineIndex()` |
| **Typ aktivity na časové ose** | Automatická událost auditu nebo explicitní událost domény zobrazená na časových osách záznamů | `defineTimelineActivityType()` |
SDK je detekuje pomocí analýzy AST v době buildu, takže organizace souborů je na vás — zažitou konvencí je `src/objects/`, `src/fields/`, `src/indexes/` a `src/timeline-activity-types/`. Stabilní UUID `universalIdentifier` vše propojují napříč nasazeními.
## Indexy (volitelné)
Aplikace mohou dodávat indexy společně se svými objekty, aby udržely opakované dotazy rychlými. Nejčastějším případem je sloupec se stavem nebo cizím klíčem, který často čtete.
```ts src/indexes/post-card-status.index.ts
import { defineIndex } from 'twenty-sdk/define';
import {
POST_CARD_UNIVERSAL_IDENTIFIER,
STATUS_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card.object';
export default defineIndex({
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff0',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: 'b6e9d2a1-5a4c-46ca-9d52-42c8f02d1ff1',
fieldUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
},
],
});
```
### Jedinečné indexy
`defineIndex` přijímá `isUnique: true` jak pro jedinečnost jednoho sloupce, tak vícesloupcovou jedinečnost. Toto je doporučené primitivum — `defineField({ isUnique: true })` je zastaralé a bude odstraněno v některém z příštích vydání.
```ts
defineIndex({
universalIdentifier: '…',
objectUniversalIdentifier: PERSON_UNIVERSAL_IDENTIFIER,
isUnique: true,
fields: [{ universalIdentifier: '…', fieldUniversalIdentifier: EMAIL_FIELD_UNIVERSAL_IDENTIFIER }],
});
```
### Další omezení
* Částečné klauzule `WHERE` zůstávají pod kontrolou administrátora — aplikace je nemohou deklarovat.
* Každý objekt je omezen na 10 vlastních indexů (indexy samotného frameworku se nepočítají).
Seřaďte pole `fields` tak, jak je má Postgres používat — nejlevější sloupec jako první, jako v telefonním seznamu. Indexy nejsou zadarmo: každý zápis do tabulky je aktualizuje. Přidávejte je jen tehdy, když máte dotaz, který je potřebuje.
<Note>
Hledáte **Application Config** nebo **Roles & Permissions**? Ty popisují samotnou aplikaci, nikoli data, která přidává — najdete je pod [Config](/l/cs/developers/extend/apps/config/overview). Hledáte **Connections** (Linear, GitHub, Slack OAuth)? Ty existují proto, aby byly volány *z* logických funkcí, a najdete je pod [Logic](/l/cs/developers/extend/apps/logic/connections).
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Vztahy
description: Propojte objekty obousměrnými relacemi MANY_TO_ONE / ONE_TO_MANY.
icon: diagram-project
---
Relace propojují dva objekty. Ve Twenty jsou relace vždy **obousměrné** — každá relace má dvě strany a každá strana je deklarována jako pole, které odkazuje na tu druhou.
| Typ vztahu | Popis | Má cizí klíč? |
| ------------- | --------------------------------------------------------------------- | ---------------------- |
| `MANY_TO_ONE` | Mnoho záznamů tohoto objektu ukazuje na jeden záznam cílového objektu | Ano (`joinColumnName`) |
| `ONE_TO_MANY` | Jeden záznam tohoto objektu má mnoho záznamů cílového objektu | Ne (inverzní strana) |
## Jak fungují relace
Každá relace vyžaduje dvě pole, která na sebe vzájemně odkazují:
1. Strana MANY_TO_ONE — je na objektu, který drží cizí klíč.
2. Strana ONE_TO_MANY — je na objektu, který vlastní kolekci.
Obě pole používají `FieldType.RELATION` a vzájemně se odkazují prostřednictvím `relationTargetFieldMetadataUniversalIdentifier`.
## Příklad: Pohlednice má mnoho příjemců
`PostCard` lze odeslat mnoha záznamům `PostCardRecipient`. Každý příjemce náleží přesně jedné pohlednici.
**Krok 1: Definujte stranu ONE_TO_MANY na PostCard** (strana "one"):
```ts src/fields/post-card-recipients-on-post-card.field.ts
import { defineField, FieldType, RelationType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_RECIPIENTS_FIELD_ID = 'a1111111-1111-1111-1111-111111111111';
// Import from the other side
import { POST_CARD_FIELD_ID } from './post-card-on-post-card-recipient.field';
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
icon: 'IconUsers',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
```
**Krok 2: Definujte stranu MANY_TO_ONE na PostCardRecipient** (strana "many" — drží cizí klíč):
```ts src/fields/post-card-on-post-card-recipient.field.ts
import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
import { POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER } from '../objects/post-card-recipient.object';
// Export so the other side can reference it
export const POST_CARD_FIELD_ID = 'b2222222-2222-2222-2222-222222222222';
// Import from the other side
import { POST_CARD_RECIPIENTS_FIELD_ID } from './post-card-recipients-on-post-card.field';
export default defineField({
universalIdentifier: POST_CARD_FIELD_ID,
objectUniversalIdentifier: POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
icon: 'IconMail',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
});
```
<Note>
**Cyklické importy:** obě relační pole odkazují na `universalIdentifier` toho druhého. Abyste předešli problémům s cyklickými importy, exportujte ID polí jako pojmenované konstanty z každého souboru a v druhém je importujte. Build systém je vyřeší v době kompilace.
</Note>
## Propojovací vztahy
Propojovací objekt spojuje jeden zdrojový záznam s jedním nebo více cílovými záznamy. Například `PostCardRecipient` může obsahovat vztah `postCard` i vztah `recipient`. V poli `ONE_TO_MANY` zdrojového objektu nastavte `junctionTargetFieldUniversalIdentifier` na pole vztahu v propojovacím objektu, které ukazuje na cíl:
```ts
export default defineField({
universalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'postCardRecipients',
label: 'Post Card Recipients',
relationTargetObjectMetadataUniversalIdentifier:
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
junctionTargetFieldUniversalIdentifier: RECIPIENT_FIELD_ID,
},
});
```
`RECIPIENT_FIELD_ID` musí identifikovat vztah `MANY_TO_ONE` nebo morfní vztah v propojovacím objektu. Systému Twenty udává, který související záznam je cílem, když funkce, jako je [rozvětvení aktivity časové osy](/l/cs/developers/extend/apps/data/timeline-activity-types#automatic-and-explicit-events), prochází propojovacím objektem.
## Vazby na standardní objekty
Chcete-li vytvořit relaci s vestavěným objektem Twenty (Person, Company atd.), použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`:
```ts src/fields/person-on-self-hosting-user.field.ts
import {
defineField,
FieldType,
RelationType,
OnDeleteAction,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER } from '../objects/self-hosting-user.object';
export const PERSON_FIELD_ID = 'c3333333-3333-3333-3333-333333333333';
export const SELF_HOSTING_USER_REVERSE_FIELD_ID = 'd4444444-4444-4444-4444-444444444444';
export default defineField({
universalIdentifier: PERSON_FIELD_ID,
objectUniversalIdentifier: SELF_HOSTING_USER_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'person',
label: 'Person',
description: 'Person matching with the self hosting user',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: SELF_HOSTING_USER_REVERSE_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'personId',
},
});
```
## Vlastnosti relačních polí
| Vlastnost | Povinné | Popis |
| ---------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------- |
| `type` | Ano | Musí být `FieldType.RELATION` |
| `relationTargetObjectMetadataUniversalIdentifier` | Ano | `universalIdentifier` cílového objektu |
| `relationTargetFieldMetadataUniversalIdentifier` | Ano | `universalIdentifier` odpovídajícího pole na cílovém objektu |
| `universalSettings.relationType` | Ano | `RelationType.MANY_TO_ONE` nebo `RelationType.ONE_TO_MANY` |
| `universalSettings.onDelete` | Pouze MANY_TO_ONE | Co se stane, když je smazán odkazovaný záznam: `CASCADE`, `SET_NULL`, `RESTRICT` nebo `NO_ACTION` |
| `universalSettings.joinColumnName` | Pouze MANY_TO_ONE | Název databázového sloupce pro cizí klíč (např. `postCardId`) |
| `universalSettings.junctionTargetFieldUniversalIdentifier` | Pouze pro propojovací ONE_TO_MANY | Pole vztahu v propojovacím objektu, které ukazuje na cíl rozvětvení |
## Vložená relační pole
Relaci můžete také deklarovat přímo uvnitř [`defineObject`](/l/cs/developers/extend/apps/data/objects). Pokud je pole vložené, vynechejte `objectUniversalIdentifier` — dědí se z nadřazeného objektu:
```ts
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCardRecipient',
// ...
fields: [
{
universalIdentifier: POST_CARD_FIELD_ID,
type: FieldType.RELATION,
name: 'postCard',
label: 'Post Card',
relationTargetObjectMetadataUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: POST_CARD_RECIPIENTS_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.CASCADE,
joinColumnName: 'postCardId',
},
},
// … other fields
],
});
```

View File

@@ -0,0 +1,212 @@
---
title: Cílení na systémová metadata
description: Vyřešte deterministické univerzální identifikátory metadat, která Twenty automaticky zřizuje u každého objektu, aby se na ně vaše aplikace mohla odkazovat bez hardcodování.
icon: gears
---
Každý objekt v Twenty má **systémová metadata**, která sami nedefinujete, například sadu polí a hlavní zobrazení seznamu s jeho sloupci. Server všechno vytvoří při zřízení objektu a tato sada roste spolu s tím, jak roste Twenty.
Protože je nedefinujete, neexistuje žádná konstanta `universalIdentifier`, kterou byste mohli importovat. Místo toho server každý identifikátor **odvozuje** deterministicky a `twenty-sdk` zpřístupňuje stejné odvozování, takže váš manifest může zjistit přesnou hodnotu, kterou server používá.
## Systémová pole
Skalární pole přítomná na každém objektu, z nichž žádné nedefinujete pomocí [`defineField()`](/l/cs/developers/extend/apps/data/extending-objects):
`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`
Jak tedy odkážete na `createdAt` jako na sloupec ve [zobrazení](/l/cs/developers/extend/apps/layout/views)?
### Problém
Od Twenty 2.19 je univerzální identifikátor systémového pole **deterministicky odvozován** serverem ze tří vstupů: univerzálního identifikátoru aplikace, univerzálního identifikátoru objektu a názvu pole. Vymyslet si id a natvrdo ho zapsat nebude fungovat: neshoduje se s ničím na serveru a synchronizace zamítne osiřelý odkaz:
```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
```
### Řešení
<Note>
`getFieldUniversalIdentifier` je k dispozici od `twenty-sdk` verze 2.21 a vyšší.
</Note>
Použijte `getFieldUniversalIdentifier` k odvození přesně stejné hodnoty, jakou používá server. Funkce přijímá tři vstupy a vrací univerzální identifikátor pole:
```ts
import { getFieldUniversalIdentifier } from 'twenty-sdk/define';
const createdAtFieldId = getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
});
```
* `applicationUniversalIdentifier` je identifikátor vaší aplikace, ten, který předáváte do [`defineApplication()`](/l/cs/developers/extend/apps/config/application).
* `objectUniversalIdentifier` je identifikátor objektu, ke kterému pole patří.
* `name` je název systémového pole, jedna z výše uvedených hodnot.
### Příklad: sloupec createdAt v zobrazení
Typickým případem je přidání sloupce `createdAt` do zobrazení některého z vašich vlastních objektů. Odvoďte identifikátor pole a odkažte na něj stejně jako na jakýkoli jiný `fieldMetadataUniversalIdentifier`:
```ts src/views/example-view.ts
import {
defineView,
getFieldUniversalIdentifier,
} from 'twenty-sdk/define';
const APPLICATION_UNIVERSAL_IDENTIFIER =
'0b04e15c-27b2-4741-9046-b32e07469072';
const MY_OBJECT_UNIVERSAL_IDENTIFIER =
'c782b61c-70fd-4c88-9cd6-4e61ab8d7591';
export default defineView({
universalIdentifier: '70f10d44-144a-4da8-8c6f-3ec2422138c0',
name: 'All records',
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: '75a90bc4-d901-4df4-85e0-af29db5e0104',
fieldMetadataUniversalIdentifier: getFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: MY_OBJECT_UNIVERSAL_IDENTIFIER,
name: 'createdAt',
}),
position: 0,
isVisible: true,
size: 200,
},
],
});
```
Tentýž odvozený identifikátor funguje všude tam, kde se očekává `fieldMetadataUniversalIdentifier`: pole zobrazení, filtry, řazení, seskupování a widgety rozložení stránky.
<Note>
Identifikátor odvozujte, neukládejte ho natvrdo. Protože server odvozuje hodnotu z
ID aplikace, ID objektu a názvu pole, volání
`getFieldUniversalIdentifier` udržuje odkaz správný i v případě, že se tyto
vstupy změní, a předchází odchylkám, pokud se samotný způsob odvozování někdy změní.
</Note>
### Systémová relační pole
<Note>
`getSystemRelationFieldUniversalIdentifier` je k dispozici od verze `twenty-sdk`
2.23 a vyžaduje server Twenty ve verzi 2.23 nebo novější.
</Note>
Kromě výše uvedených skalárních systémových polí server také na každém objektu poskytuje čtyři **systémová relační pole**: `timelineActivities`, `attachments`, `noteTargets` a `taskTargets`, z nichž každé ukazuje na odpovídající standardní relační objekt.
Tato pole nejsou řešena pomocí `getFieldUniversalIdentifier`: jejich identifikátor je odvozen **bez ohledu na název** z objektu, který pole obsahuje, a z objektu, na který pole odkazuje. Tím pádem přejmenování objektu nikdy nezmění identifikátory jeho relačních polí.
Použijte `getSystemRelationFieldUniversalIdentifier` k jejich vyřešení:
```ts
import {
getSystemRelationFieldUniversalIdentifier,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
// rocket.attachments — the relation field hosted on your custom object
const rocketAttachmentsFieldId = getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
});
```
* `objectUniversalIdentifier` je objekt, který toto pole **obsahuje**.
* `relationTargetObjectUniversalIdentifier` je objekt, na který toto pole **ukazuje**.
Směr je zakódovaný pořadím argumentů. Pro vyřešení opačné strany (např. `attachment.targetRocket`, morph pole, které server vytvoří na standardním relačním objektu), je prohoďte:
```ts
// attachment.targetRocket — the reverse morph field on Attachment
const attachmentTargetRocketFieldId =
getSystemRelationFieldUniversalIdentifier({
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment.universalIdentifier,
relationTargetObjectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
});
```
Stejně jako u skalárních systémových polí funguje vyřešené ID všude tam, kde je očekáváno `fieldMetadataUniversalIdentifier`.
## Systémová zobrazení
<Note>
`getSystemViewUniversalIdentifier` a `getSystemViewFieldUniversalIdentifier`
jsou k dispozici od verze `twenty-sdk` 2.26 a vyžadují server Twenty ve
verzi 2.26 nebo novější.
</Note>
Server také pro každý objekt zřizuje **systémové zobrazení**: hlavní zobrazení seznamu (`All {objectLabelPlural}`, s klíčem `ViewKey.INDEX` (odvozeným pomocí `SYSTEM_VIEW_KEYS.INDEX`)), s jedním sloupcem pro každé zobrazitelné pole. Stejně jako u systémových relačních polí jsou jejich identifikátory odvozovány **nezávisle na názvu**, takže přejmenování objektu nebo pole je nikdy nezmění.
Použijte `getSystemViewUniversalIdentifier` k vyřešení zobrazení:
```ts
import {
getSystemViewUniversalIdentifier,
SYSTEM_VIEW_KEYS,
} from 'twenty-sdk/define';
const rocketIndexViewId = getSystemViewUniversalIdentifier({
objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER,
viewKey: SYSTEM_VIEW_KEYS.INDEX,
});
```
* `objectMetadataApplicationUniversalIdentifier` je aplikace, která vlastní **objekt**, podle které je zobrazení namespacované.
* `objectUniversalIdentifier` je objekt, který zobrazení vypisuje.
* `viewKey` je diskriminátor systémového zobrazení: `SYSTEM_VIEW_KEYS.INDEX` pro hlavní zobrazení seznamu, `SYSTEM_VIEW_KEYS.FIELDS_WIDGET` pro zobrazení widgetu polí na stránce záznamu. Určuje klíč pro odvozování; pouze `INDEX` je také uložen v řádku zobrazení.
Vyhodnocené ID funguje všude tam, kde se očekává `viewUniversalIdentifier`, například u položky postranního panelu [`NavigationMenuItemType.VIEW`](/l/cs/developers/extend/apps/layout/navigation-menu-items). Pro pouhé otevření hlavního seznamu objektu preferujte `NavigationMenuItemType.OBJECT` s `targetObjectUniversalIdentifier`: nevyžaduje žádné odvozování.
`getSystemViewFieldUniversalIdentifier` vyhodnotí jeden konkrétní **sloupec** v systémovém zobrazení na základě zobrazení a pole, které zobrazuje:
```ts
import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define';
const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({
fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
viewUniversalIdentifier: rocketIndexViewId,
fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER,
});
```
Všimněte si prvního argumentu: sloupec je namespacován podle aplikace, která vlastní **pole, které zobrazuje**, nikoli podle aplikace, která vlastní zobrazení. Pole, které vaše aplikace přidá ke standardnímu objektu, má svůj sloupec odvozený pod vaší aplikací, a to v rámci zobrazení vlastněného Twenty.
<Warning>
Systémová zobrazení a jejich sloupce jsou **vlastněna serverem**: vyhodnocujte jejich identifikátory pro účely odkazování, nikdy pro jejich deklaraci. `key` u
[`defineView()`](/l/cs/developers/extend/apps/layout/views) je zastaralý a
ignorovaný, takže manifestové zobrazení nikdy nemůže nárokovat klíč `INDEX` a server už zřizuje sloupec pro každé pole, které přidáte, takže deklarovat vlastní
`defineViewField()` pro stejné pole v systémovém zobrazení s tím bude v konfliktu.
</Warning>
## Standardní objekty Twenty
U **standardního** objektu Twenty (Person, Company, Opportunity, …) nemusíte nic odvozovat: identifikátory jsou předpočítané konstanty, které můžete přímo importovat, a to jak pro pole, tak pro zobrazení.
```ts
import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier
// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier
```
Použijte pomocné funkce uvedené výše v případě, kdy je objekt definován **vaší aplikací** pomocí [`defineObject()`](/l/cs/developers/extend/apps/data/objects) a žádná taková konstanta neexistuje.
<Note>
`name` je **výchozí** pole, nikoli systémové pole. Má svůj vlastní natvrdo daný
univerzální identifikátor a neodvozuje se pomocí
`getFieldUniversalIdentifier`. U objektů, které definujete, odkazujte na pole
`name` pomocí identifikátoru, který jste mu dali v `defineObject()`.
</Note>

View File

@@ -0,0 +1,184 @@
---
title: Typy aktivit na časové ose
description: Definujte automatické události auditu a explicitní události aplikace, které se zobrazují na časových osách záznamů.
icon: clock-rotate-left
---
Typ aktivity na časové ose definuje stabilní slovník pro událost zobrazenou na časové ose záznamu. Standardní objekty a objekty aplikace používají stejný kontrakt: typ má popisek a ikonu, může volitelně deklarovat, kdy je vyvolán, a může se volitelně vykreslovat prostřednictvím jedné z [front components](/l/cs/developers/extend/apps/layout/front-components) vaší aplikace.
<Note>
Typy aktivit na časové ose jsou ve verzi beta a brzy přijdou ve Twenty 2.34. Rozhraní
API se může vyvíjet, zatímco se učíme z případů použití vývojářů aplikací.
</Note>
Vytvořte jej pomocí scaffolderu:
```bash filename="Terminal"
yarn twenty dev:add timelineActivityType
```
Nebo jej definujte přímo:
```ts filename="src/timeline-activity-types/post-card-created.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7500',
name: 'postCardCreated',
label: 'created a post card',
icon: 'IconMail',
emit: {
on: 'created',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
},
});
```
## Automatické a explicitní události
Přidejte "emitovat", když by mělo dvacet automaticky vytvářet tento typ. `emit.on` podporuje `created`, `updated`, `deleted`, `restored`, `linked`, a `unlinked`, zatímco `emit.objectUniversalIdentifier` identifikuje zdrojový objekt. Pouze jeden efektivní typ dokáže zvládnout stejný emit klíč — `on`, objekt a volitelný prostřednictvím vztahu — v pracovním prostoru.
Bez `emit.through je událost zapsána na vlastní časové ose zdrojového záznamu. Chcete-li ji rozdmýchat se souvisejícími záznamy, nastavte `emit.through . elationFieldUniversalIdentifier\` buď přímé mnoho-jedno-vztahy, nebo [jedno-mnoho-vztažení](/l/cs/developers/extend/apps/data/relations#junction-relations) na zdrojovém objektu. Vztahy s morfem se rozdmýchají každému členu jejich morfské skupiny, takže jediná deklarace se může zaměřit na několik typů objektů.
Pro přímý vztah vytváří vytváření nebo obnovování zdrojového záznamu `linked`, protože jeho smazání produkuje `unlinked`, a opakováním vztahu vznikne `nespojený` na předchozí cíl plus `linked` na nový cíl. Další aktualizace zdroje nevytvářejí události odkazů. Jedná se o smlouvu používanou přílohami, jejichž cílem je přímý morfologický vztah.
Pro vztah mezi dvěma uzly identifikuje vztah od objektu ke křižovatce k cíli. Vytváření nebo smazání křižovatky produkuje `linked` nebo `unlinked`. Repointing either side of the junction row also produkuje událost spojení; aktualizace nespřízněných přechodových polí nikoli.
Události `linked` a `unlinked` vyžadují `emit.through`, protože jejich spouštěč je změnou konfigurované přímé nebo křižovatkové vztahy.
Jedná se například o stejnou obecnou smlouvu používanou poznámkami, úkoly, zprávami a událostmi v kalendáři:
```ts filename="src/timeline-activity-types/post-card-linked.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
import { POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER } from '../fields/post-card-recipients-on-post-card.field';
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7502',
name: 'postCardLinked',
label: 'received a post card',
icon: 'IconMail',
emit: {
on: 'linked',
objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
through: {
relationFieldUniversalIdentifier:
POST_CARD_RECIPIENTS_FIELD_UNIVERSAL_IDENTIFIER,
},
},
});
```
"Aktualizováno" prostřednictvím události je ve výchozím nastavení vypnuto pro každou zdrojovou aktualizaci. Nastavte `emit.through.triggerFieldUniversalIdentifiers` pokud se změní pouze na vybraná zdrojová pole, měla by se zobrazit v cílovém časovém horizontu.
Vypni `emit` pro explicitní doménu, kterou tvá logická funkce sama vytváří. Tím se zamezí vytvoření automatického řádku pro audit i výslovného řádku pro tutéž operaci.
```ts filename="src/timeline-activity-types/post-card-sent.ts"
import { defineTimelineActivityType } from 'twenty-sdk/define';
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
});
```
Vytvořte explicitní události pomocí [`createTimelineActivity()`](/l/cs/developers/extend/apps/logic/logic-functions#create-a-timeline-activity). Kód aplikace používá stabilní univerzální identifikátory. Dvacet vyřeší ID metadat specifických pro instalaci.
## Vlastní vykreslování
Bez přední komponenty vykreslí dvacet nativní generický řádek z popisku typu, ikony a propojených objektů. To funguje pro standardní a vlastní objekty bez vykreslování konkrétního objektu.
Pro vlastní detaily nastavte `frontComponentUniversalIdentifier` na přední komponentu vlastněnou stejnou aplikací:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
name: 'postCardSent',
label: 'sent a post card',
icon: 'IconSend',
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Rodičovská řada zůstává zkrachovalou prezentací. Dvacet připojuje přední díl až poté, co uživatel rozšíří tento řádek, zabraňuje tomu, aby se pískovací box a pracovník pro každou viditelnou událost. Uvnitř komponenty zavolejte `useTimelineActivityId()` z `dvacet sdk/front-component` a přečtěte si ID řádku a načtěte všechna data, která potřebujete. Vrátí `null` pokud je komponenta vykreslena mimo časový řádek.
## Přepsání prezentace jiné aplikace
Aplikace vlastní automatické časové osy pro své vlastní objekty. Chcete-li přizpůsobit událost na objektu vlastněném jinou aplikací, deklarujte existující typ výslovně pomocí `replacesTimelineActivityTypeUniversalIdentifier`:
```ts
export default defineTimelineActivityType({
universalIdentifier: 'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7503',
name: 'companyCreatedWithDeploymentContext',
label: 'was created from a deployment',
emit: {
on: 'created',
objectUniversalIdentifier: COMPANY_UNIVERSAL_IDENTIFIER,
},
replacesTimelineActivityTypeUniversalIdentifier:
RECORD_CREATED_TIMELINE_ACTIVITY_TYPE_UNIVERSAL_IDENTIFIER,
frontComponentUniversalIdentifier: '88c15ae2-5f87-4a6b-b48f-1974bbe62eb7',
});
```
Přepsání musí obsahovat `emit`, protože nahrazuje existující automatický slot emit namísto typu pouze pro explicitní. Uvedený typ musí patřit k aplikaci cílového objektu a popsat stejnou akci a cestu. Odstranění přepsání aplikace obnoví základní typ; odstranění základního typu zakáže přepsání.
## Pracovní prostor přepíše a ztiší
Správci pracovního prostoru mohou změnit prezentaci typu nebo ztlumit jeho automatické a explicitní události bez vytvoření aplikace. Použijte metadata API mutaci `updateTimelineActivityType` s ID specifického typu instalace:
```graphql
mutation CustomizeTimelineActivityType(
$id: UUID!
$label: String
$icon: String
$isActive: Boolean
) {
updateTimelineActivityType(
input: { id: $id, label: $label, icon: $icon, isActive: $isActive }
) {
id
label
icon
isActive
}
}
```
`label` a `icon` jsou uloženy jako přepisy pracovního prostoru, takže pozdější aktualizace aplikace nepřepisují volby správce. Nastavte `isActive` na `false` pro zastavení automatických emisí a odmítnutí nových explicitních událostí tohoto typu. Stávající řádky zůstávají viditelné.
Obnovit výchozí nastavení aplikace, včetně aktivního stavu:
```graphql
mutation ResetTimelineActivityType($id: UUID!) {
resetTimelineActivityType(id: $id) {
id
label
icon
isActive
}
}
```
## Konfigurační pole
| Pole | Povinné | Popis |
| ------------------------------------------------- | --------- | --------------------------------------------------------------------------------------- |
| `universalIdentifier` | Ano | Stabilní UUID pro tento typ zařízení a modernizace |
| `name` | Ano | Programový název lokální pro aplikaci; musí být v rámci aplikace jedinečný |
| `label` | Ano | Uživatelsky orientovaný text akce používaný nativním přehrávačem |
| `icon` | Ne | Dvacet jmen ikony zobrazených vedle události |
| `emit` | Ne | Automatické prohlášení o emisích; v případě typů pouze s výslovnou výjimkou |
| `emit.on` | S emailem | Auditní opatření, která způsobí, že tento typ bude napsán |
| `emit.objectUniversalIdentifier` | S emailem | Objekt, jehož záznamy vypouštějí tento typ |
| `emit.through.relationFieldUniversalIdentifier` | Ne | Vztah k přímému nebo rozvodnému vedení používanému k ventilaci příslušných časových řad |
| `emit.through.triggerFieldUniversalIdentifiers` | Ne | Zdrojová pole, která mohou spustit `aktualizované` prostřednictvím události |
| `frontComponentUniversalIdentifier` | Ne | Čelní díl vlastněný App připojený při rozšíření řádku |
| `replacesTimelineActivityTypeUniversalIdentifier` | Ne | Stávající typ emit nahrazen; vyžaduje `emit` |
Dvacet snímků sémantické identity a trvanlivé prezentace při vytvoření události. Historické řádky zachovávají svou původní akci a význam objektu. Zatímco je tento typ nainstalován, jeho aktuální přeložený popisek, ikona a hlavní komponenty vykreslují živě; po odinstalování udržuje záložní snímek řádek čitelný. Rozlišení používá univerzální identifikátor, takže prezentace také přežívá přeinstalování aplikace.

View File

@@ -0,0 +1,102 @@
---
title: Pojmy
description: Jak aplikace Twenty fungují — model entit, sandboxing a životní cyklus instalace.
icon: sitemap
---
Aplikace Twenty jsou balíčky TypeScriptu, které rozšiřují váš pracovní prostor o vlastní objekty, logiku, komponenty UI a funkce AI. Běží na platformě Twenty s plnou izolací (sandboxingem) a řízením oprávnění.
## Jak aplikace fungují
Aplikace je kolekce **entit** deklarovaných pomocí funkcí `defineEntity()` z balíčku `twenty-sdk`. SDK tyto deklarace detekuje pomocí analýzy AST při sestavení a vytváří **manifest** — úplný popis toho, co vaše aplikace přidává do pracovního prostoru. Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
```
your-app/
├── src/
│ ├── application-config.ts ← defineApplication (required, one per app)
│ ├── roles/ ← defineRole
│ ├── objects/ ← defineObject
│ ├── fields/ ← defineField
│ ├── logic-functions/ ← defineLogicFunction
│ ├── front-components/ ← defineFrontComponent
│ ├── skills/ ← defineSkill
│ ├── agents/ ← defineAgent
│ ├── views/ ← defineView
│ ├── navigation-menu-items/ ← defineNavigationMenuItem
│ └── page-layouts/ ← definePageLayout
├── public/ ← Static assets (images, icons)
└── package.json
```
<Note>
**Uspořádání souborů je na vás.** Detekce entit je založená na AST — SDK najde volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází. Výše uvedená struktura složek je konvence, nikoli požadavek.
</Note>
## Typy entit
| Entita | Účel | Dokumentace |
| ----------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Aplikace** | Identita aplikace, výchozí role, proměnné | [Application Config](/l/cs/developers/extend/apps/config/application) |
| **Role** | Sady oprávnění pro objekty a pole | [Roles & Permissions](/l/cs/developers/extend/apps/config/roles) |
| **Objekt** | Vlastní typy záznamů s poli | [Objects](/l/cs/developers/extend/apps/data/objects) |
| **Pole** | Přidání polí k objektům z jiných aplikací | [Extending Objects](/l/cs/developers/extend/apps/data/extending-objects) |
| **Vztah** | Obousměrná propojení mezi objekty | [Relations](/l/cs/developers/extend/apps/data/relations) |
| **Logická funkce** | TypeScript na straně serveru se spouštěči | [Logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) |
| **Dovednost** | Znovupoužitelné pokyny pro AI agenty | [Dovednosti a agenti](/l/cs/developers/extend/apps/logic/skills-and-agents) |
| **Agent** | AI asistenti s vlastními prompty | [Dovednosti a agenti](/l/cs/developers/extend/apps/logic/skills-and-agents) |
| **Poskytovatel připojení** | Přihlašovací údaje OAuth pro externí rozhraní API třetích stran | [Connections](/l/cs/developers/extend/apps/logic/connections) |
| **Zobrazení** | Předkonfigurovaná zobrazení seznamu záznamů | [Views](/l/cs/developers/extend/apps/layout/views) |
| **Položka navigační nabídky** | Vlastní položky postranního panelu | [Navigation Menu Items](/l/cs/developers/extend/apps/layout/navigation-menu-items) |
| **Rozvržení stránky** | Karty a widgety na stránce s podrobnostmi záznamu | [Page Layouts](/l/cs/developers/extend/apps/layout/page-layouts) |
| **Frontendová komponenta** | Izolované React UI uvnitř Twenty | [Frontendové komponenty](/l/cs/developers/extend/apps/layout/front-components) |
| **Sdílené závislosti** | Závislosti sdílené frontendovými komponentami aplikace, definované v souboru package.json | [Sdílené závislosti](/l/cs/developers/extend/apps/layout/front-components#sharing-dependencies-across-front-components) |
| **Položka příkazové nabídky** | Rychlé akce a položky Cmd+K | [Command Menu Items](/l/cs/developers/extend/apps/layout/command-menu-items) |
## Izolace (sandboxing)
* **Logické funkce** běží v izolovaných procesech Node.js na serveru. K datům přistupují pouze prostřednictvím typovaného klienta API, který vystupuje buď jako osoba, jež spuštění vyvolala, nebo jako samotná aplikace. Viz [čí přístup volání používá](/l/cs/developers/extend/apps/logic/logic-functions#whose-access-a-call-uses).
* **Frontendové komponenty** běží ve Web Workerech s využitím Remote DOM — jsou oddělené od hlavní stránky, ale vykreslují nativní prvky DOM (nikoli iframy). Komunikují s Twenty prostřednictvím hostitelského API pro předávání zpráv.
* **Oprávnění** jsou vynucována na úrovni API. Vkládají se dva tokeny běhového prostředí, oba odvozené z role deklarované pomocí `defineApplicationRole()`; výchozí token je dále omezen na to, co může provést osoba, která spuštění vyvolala, kdykoli za spuštěním stojí nějaká osoba.
## Životní cyklus aplikace
```
┌─────────────────────────────────────────────────────────┐
│ Development │
│ npx create-twenty-app → yarn twenty dev (live sync) │
├─────────────────────────────────────────────────────────┤
│ Build & Deploy │
│ yarn twenty dev:build → yarn twenty app:publish │
├─────────────────────────────────────────────────────────┤
│ Install flow │
│ upload → [pre-install] → metadata migration → │
│ generate SDK → [post-install] → … → [uninstall] │
├─────────────────────────────────────────────────────────┤
│ Publish │
│ npm publish → appears in Twenty marketplace │
└─────────────────────────────────────────────────────────┘
```
* **`yarn twenty dev`** — sleduje vaše zdrojové soubory a průběžně synchronizuje změny s připojeným serverem Twenty. Typovaný klient API se při změně schématu automaticky znovu vygeneruje.
* **`yarn twenty dev:build`** — zkompiluje TypeScript, zabalí logické funkce a frontendové komponenty pomocí esbuild a vytvoří manifest.
* **Pre/post-install a odinstalační hooky** — volitelné funkce, které běží během instalace nebo těsně před odinstalací. Podrobnosti viz [Install Hooks](/l/cs/developers/extend/apps/config/install-hooks).
## Další kroky
<CardGroup cols={2}>
<Card title="Konfigurace" icon="screwdriver-wrench" href="/l/cs/developers/extend/apps/config/overview">
Identita aplikace, výchozí role a instalační a odinstalační hooky.
</Card>
<Card title="Data" icon="database" href="/l/cs/developers/extend/apps/data/overview">
Objekty, pole a obousměrné relace.
</Card>
<Card title="Logika" icon="bolt" href="/l/cs/developers/extend/apps/logic/overview">
Logické funkce, dovednosti, agenti a připojení přes OAuth.
</Card>
<Card title="Rozvržení" icon="table-columns" href="/l/cs/developers/extend/apps/layout/overview">
Zobrazení, navigace, rozvržení stránek, frontendové komponenty.
</Card>
<Card title="Operace" icon="rocket" href="/l/cs/developers/extend/apps/operations/overview">
CLI, testování, vzdálené repozitáře, CI a publikování vaší aplikace.
</Card>
</CardGroup>

View File

@@ -0,0 +1,87 @@
---
title: Lokální server
description: Spravujte lokální Docker server Twenty – spouštění, zastavení, upgrade, paralelní testovací instance a ruční nastavení SDK.
icon: server
---
## Správa lokálního serveru
K ovládání lokálního kontejneru Twenty použijte `yarn twenty docker:*`:
| Příkaz | K čemu slouží |
| -------------------------------------- | ---------------------------------------------- |
| `yarn twenty docker:start` | Spustí server (v případě potřeby stáhne image) |
| `yarn twenty docker:start 2.2.0` | Spustit konkrétní verzi serveru |
| `yarn twenty docker:start --port 3030` | Spustí na vlastním portu |
| `yarn twenty docker:stop` | Zastaví server (zachová data) |
| `yarn twenty docker:status` | Zobrazí URL, verzi a přihlašovací údaje |
| `yarn twenty docker:logs` | Streamuje protokoly serveru |
| `yarn twenty docker:reset` | Vymaže data a začne znovu |
| `yarn twenty docker:upgrade` | Stáhne nejnovější image `twenty-app-dev` |
| `yarn twenty docker:upgrade 2.2.0` | Aktualizuje na konkrétní verzi |
Data přetrvávají při restartech ve dvou svazcích Dockeru (`twenty-app-dev-data` pro PostgreSQL, `twenty-app-dev-storage` pro soubory). Pomocí `reset` vymažte vše.
## Fixace verze serveru
Když není předána žádná verze, `docker:start` určí verzi z rozsahu `engines.twenty` vaší aplikace v `package.json` — stejného rozsahu, vůči kterému server ověřuje, když je vaše aplikace nainstalována. Spustí nejnovější publikovaný image `twenty-app-dev`, který splňuje tento rozsah, a pokud pole chybí nebo žádná publikovaná verze neodpovídá, použije `latest`:
```json filename="package.json"
{
"engines": {
"twenty": ">=2.2.0"
}
}
```
Předáním verze můžete rozsah pro jedno spuštění přepsat: `yarn twenty docker:start 2.3.0`. Pokud už kontejner existuje v jiné verzi, `docker:start` jej na místě aktualizuje (znovu vytvoří kontejner při zachování vašich datových svazků).
## Aktualizace obrazu serveru
`yarn twenty docker:upgrade` stáhne nejnovější image, porovná digesty a znovu vytvoří kontejner pouze v případě, že se skutečně něco změnilo. Svazky zůstanou zachovány — nahradí se pouze kontejner. Pokud byl stažen nový image a kontejner běžel, upgrade automaticky spustí nový kontejner; poté spusťte `yarn twenty docker:start`, abyste počkali, než bude ve stavu 'healthy'.
```bash filename="Terminal"
yarn twenty docker:upgrade # Latest
yarn twenty docker:upgrade 2.2.0 # Specific version
```
Běžící verzi ověříte pomocí `yarn twenty docker:status` (zobrazí `APP_VERSION` zabudovanou v kontejneru).
## Spuštění paralelní testovací instance
Předejte `--test` libovolnému příkazu `docker:*` pro správu druhé, plně izolované instance — užitečné pro integrační testy nebo experimentování bez zásahu do vašich hlavních vývojových dat:
| Příkaz | K čemu slouží |
| ----------------------------------- | ------------------------------------------------ |
| `yarn twenty docker:start --test` | Spustí testovací instanci (výchozí port je 2021) |
| `yarn twenty docker:stop --test` | Zastaví ji |
| `yarn twenty docker:status --test` | Zobrazí její stav |
| `yarn twenty docker:logs --test` | Streamuje její protokoly |
| `yarn twenty docker:reset --test` | Vymaže její data |
| `yarn twenty docker:upgrade --test` | Aktualizuje její image |
Testovací instance má vlastní kontejner (`twenty-app-dev-test`), svazky (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`) a konfiguraci — běží souběžně s vaší hlavní instancí bez konfliktů. Zkombinujte `--test` s `--port` pro změnu výchozího portu 2021.
## Ruční nastavení (bez generátoru kostry)
Pokud přidáváte SDK do existujícího projektu, generátor kostry přeskočte:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
```
Přidejte skript do `package.json`:
```json filename="package.json"
{
"scripts": {
"twenty": "twenty"
}
}
```
Nyní můžete spouštět `yarn twenty dev`, `yarn twenty docker:start` a další.
<Note>
Neinstalujte `twenty-sdk` globálně — nainstalujte jej v každém projektu zvlášť, aby každá aplikace používala svou vlastní verzi.
</Note>

View File

@@ -0,0 +1,79 @@
---
title: Struktura projektu
description: Co obsahuje vygenerovaná aplikace Twenty — soubory, složky a k čemu každý z nich slouží.
icon: folder-tree
---
Nová aplikace vygenerovaná pomocí `npx create-twenty-app` vypadá takto:
```text filename="my-twenty-app/"
my-twenty-app/
package.json
src/
application-config.ts # Required — your app's entry point
default-role.ts # Permissions for logic functions
constants/
universal-identifiers.ts # Auto-generated UUIDs and metadata
front-components/
main-page.tsx # Welcome page component
navigation-menu-items/
main-page.navigation-menu-item.ts # Sidebar entry for the welcome page
page-layouts/
main-page.page-layout.ts # Standalone page hosting the component
__tests__/
application-config.test.ts # Unit test
global-setup.ts # Integration test setup (sync + uninstall)
schema.integration-test.ts # Integration test against a live server
.github/workflows/
ci.yml # Lint, typecheck, unit + integration tests
cd.yml # Deploy + install on push to main
publish.yml # Publish to npm on version tags (with provenance)
public/
logo.svg # Static assets
vitest.config.ts # Integration test runner config
vitest.unit.config.ts # Unit test runner config
tsconfig.json, tsconfig.spec.json
.nvmrc, .yarnrc.yml, .oxlintrc.json
README.md, AGENTS.md, CLAUDE.md, CHANGELOG.md, SETUP.md
```
## Klíčové soubory
| Soubor / Složka | Účel |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `src/application-config.ts` | **Povinné.** Hlavní konfigurační soubor vaší aplikace. |
| `src/default-role.ts` | Výchozí role, která řídí, k čemu mohou vaše logické funkce přistupovat. |
| `src/constants/universal-identifiers.ts` | Automaticky generovaná UUID a metadata (zobrazovaný název, popis). |
| `src/front-components/`, `src/navigation-menu-items/`, `src/page-layouts/` | Výchozí uvítací stránka: frontendová komponenta vykreslená samostatným rozložením stránky, dostupná z postranního panelu. |
| `src/__tests__/` | Jednotkový test plus integrační test (s jeho globálním nastavením), který synchronizuje aplikaci proti skutečnému serveru. |
| `public/` | Statické soubory (obrázky, písma) doručované vaší aplikací. |
| `AGENTS.md` / `CLAUDE.md` | Pokyny pro agenty AI pro psaní kódu, kteří pracují na aplikaci. |
| `CHANGELOG.md` / `SETUP.md` | Seznam významných změn a pokyny k nastavení pro lokální vývoj. |
<Note>
**Uspořádání souborů je na vás.** Výše uvedené složky jsou konvence — SDK detekuje entity pomocí analýzy AST u volání `export default defineEntity(...)` bez ohledu na to, kde se soubor nachází.
</Note>
## Závislosti
Oba balíčky Twenty SDK patří pod `devDependencies`, ne pod `dependencies`:
```json filename="package.json"
{
"dependencies": {},
"devDependencies": {
"twenty-client-sdk": "2.20.0",
"twenty-sdk": "2.20.0",
"twenty-ui": "1.0.0-alpha.1"
}
}
```
Nástroj pro scaffolding připne `twenty-sdk` a `twenty-client-sdk` ke své vlastní verzi — při aktualizaci udržujte tyto dva balíčky synchronizované.
* **`twenty-sdk`** dodává `twenty` CLI a nástroje pro sestavení/scaffolding. Běží pouze při vývoji a sestavování a nikdy není importován za běhu zveřejněné aplikace.
* **`twenty-client-sdk`** je importován kódem vaší aplikace (`CoreApiClient`, `MetadataApiClient`, `RestApiClient`), ale Twenty ho poskytuje za běhu — logické funkce ho získávají z vygenerované SDK vrstvy a front-endové komponenty ho načítají z modulů poskytovaných serverem. Vaše nainstalovaná kopie se používá pouze pro kontrolu typů a build v době nasazení, takže ji nikdy není potřeba přibalit do nasazeného balíčku.
Ponechání kteréhokoli balíčku pod `dependencies` ho vtáhne do runtime balíčku nainstalované aplikace, kde je jen mrtvou vahou. `twenty dev:build` vypíše varování, pokud je kterýkoli z nich stále uveden pod `dependencies`.
Vlastní runtime závislosti vaší aplikace (knihovny, které vaše logické funkce skutečně importují za běhu) přidejte jako obvykle pod `dependencies`. Udržujte strom štíhlý: `dependencies` se instalují do běhové vrstvy s limitem 250 MB po rozbalení a při jeho překročení synchronizace nebo instalace selže s chybou překročení velikosti závislostí — UI knihovny a vývojářské nástroje patří do `devDependencies`.

View File

@@ -0,0 +1,166 @@
---
title: Rychlý start
icon: rocket
description: Vytvořte svou první aplikaci Twenty během několika minut.
---
## Předpoklady
* **Node.js 24.5+** — [Stáhnout](https://nodejs.org/)
* **Yarn 4** — je součástí Node.js prostřednictvím Corepacku. Povolte jej: `corepack enable`
* **Docker** — [Stáhnout](https://www.docker.com/products/docker-desktop/). Nutné pro spuštění lokálního serveru Twenty. Přeskočte, pokud už máte Twenty spuštěné jinde.
Vytváření aplikace Twenty má tři fáze. Generátor kostry je spojuje do jediného příkazu pro ideální scénář, ale každá fáze je samostatný koncept — když se něco nepovede, znalost aktuální fáze napoví, co opravit.
| Fáze | Co děláte | Nástroj | Výsledek |
| ---------------------- | -------------------------------------------------------- | ----------------------------------- | -------------------------------------------- |
| **1. Vytvořit kostru** | Vygenerovat zdrojový kód aplikace | `npx create-twenty-app` | Projekt v TypeScriptu na disku |
| **2. Spustit server** | Spustit server Twenty, do kterého se bude synchronizovat | Docker + `yarn twenty docker:start` | Běžící instance Twenty |
| **3. Synchronizovat** | Živě synchronizovat kód na server | `yarn twenty dev` | Vaše změny se objeví v uživatelském rozhraní |
---
## Fáze 1 — Vytvořte kostru projektu
Vytvořte novou aplikaci ze šablony:
```bash filename="Terminal"
npx create-twenty-app@latest my-twenty-app
```
Generátor kostry je neinteraktivní: název adresáře se stane názvem aplikace. Předejte `--display-name` a `--description` pro přizpůsobení vygenerovaných metadat (později je můžete také upravit v `src/constants/universal-identifiers.ts`). Tím se v `my-twenty-app/` vytvoří projekt v TypeScriptu se startovacím `application-config.ts`, výchozí rolí, pracovními postupy CI/CD a integračním testem.
**Po této fázi:** máte na svém počítači zdrojový kód aplikace. Zatím neběží — to je předmětem Fáze 2.
---
## Fáze 2 — Spusťte lokální server Twenty
Vaše aplikace potřebuje server Twenty, do kterého se bude synchronizovat. Server je plnohodnotná instance Twenty — UI, GraphQL API, PostgreSQL — běžící lokálně v Dockeru. Váš lokální kód nahrává své definice na tento server, díky čemuž se objeví v UI.
Nástroj pro vytváření projektů to spustí za vás: při spuštěném Dockeru stáhne image `twentycrm/twenty-app-dev`, spustí ji na portu `2020` a autentizuje CLI vůči předpřipravenému demo workspace (`tim@apple.dev`) — není vyžadováno žádné přihlášení.
Chcete-li se místo toho připojit k existujícímu serveru Twenty, předejte `--url \<your-server-url>`. Vzdálené servery se ověřují pomocí OAuth: otevře se prohlížeč, abyste se mohli přihlásit a kliknout na **Authorize**, čímž udělíte nástroji CLI přístup k vašemu pracovnímu prostoru. (Můžete se také rozhodnout pro OAuth lokálně pomocí `--authentication-method oauth` — přihlaste se pomocí `tim@apple.dev` / `tim@apple.dev`.)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Přihlašovací obrazovka Twenty" />
</div>
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Autorizační obrazovka Twenty CLI" />
</div>
Váš terminál potvrdí, že je vše nastaveno.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplikace byla úspěšně vygenerována" />
</div>
**Po této fázi:** máte spuštěný server Twenty na [http://localhost:2020](http://localhost:2020) a vaše CLI má oprávnění se k němu synchronizovat.
<Note>
Pokud Docker není nainstalovaný nebo neběží, generátor kostry vám sdělí správný příkaz pro spuštění ve vašem operačním systému. Jakmile Docker poběží, můžete pokračovat pomocí `yarn twenty docker:start` — není potřeba znovu vytvářet kostru.
</Note>
---
## Fáze 3 — Synchronizujte své změny
Toto je vnitřní smyčka, ve které strávíte většinu času.
```bash filename="Terminal"
cd my-twenty-app
yarn twenty dev
```
Tento proces sleduje `src/`, při každé změně znovu sestaví a synchronizuje výsledek na server. Upravte soubor, uložte a během několika vteřin se změna projeví na serveru. V terminálu uvidíte panel se stavem v reálném čase.
Pro podrobnější výstup (protokoly sestavení, požadavky na synchronizaci, stopy chyb) přidejte `--verbose`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.png" alt="Výstup terminálu ve vývojovém režimu" />
</div>
Otevřete [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). Vaše aplikace by měla být uvedena v části **Your Apps**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Seznam Your Apps se zobrazenou aplikací My twenty app" />
</div>
Klikněte na **My twenty app** a zobrazí se jeho **registrace aplikace** — záznam na úrovni serveru, který popisuje vaši aplikaci (název, identifikátor, přihlašovací údaje OAuth, zdroj). Jedna registrace může být nainstalována ve více pracovních prostorech na stejném serveru.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Podrobnosti registrace aplikace" />
</div>
Klikněte na **View installed app**, abyste zobrazili instalaci v pracovním prostoru. Karta **About** zobrazuje verzi a možnosti správy.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Nainstalovaná aplikace" />
</div>
**Po této fázi:** máte průběžný vývojový cyklus. Upravte libovolný soubor v `src/` a projeví se to v UI.
### Jednorázová synchronizace pro CI a skripty
Použijte `plan` a `apply` ke spuštění stejné pipeline jednorázově, bez watcheru:
```bash filename="Terminal"
yarn twenty plan # preview the metadata changes without applying them
yarn twenty apply # show the plan, then apply it
```
| Příkaz | Chování | Kdy použít |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `yarn twenty dev` | Sleduje změny a při každé změně znovu synchronizuje. Běží, dokud jej nezastavíte. | Interaktivní lokální vývoj. |
| `yarn twenty apply` | Jedno sestavení + synchronizace, ukončí se s kódem `0` při úspěchu, `1` při chybě. Vyžádá si potvrzení destruktivních změn (předejte `--force`, abyste to přeskočili). | CI, pre-commit hooky, AI agenti, skriptované pracovní postupy. |
| `yarn twenty plan` | Sestaví a vypíše změny metadat **bez jejich použití**. | Kontrola toho, co by synchronizace změnila, ještě před jejím potvrzením. |
Všechny režimy vyžadují autentizovaný vzdálený server. Více informací o `plan` najdete v části [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan).
<Note>
`yarn twenty dev --once` a `yarn twenty dev --once --dry-run` jsou zastaralé aliasy pro `yarn twenty apply` a `yarn twenty plan`.
</Note>
### Možnosti vývojového režimu
| Přepínač | Popis |
| ------------------------------------- | --------------------------------------------------------------------------------------------- |
| `--force` | Aplikuje destruktivní změny (mazání) bez potvrzení. |
| `--debounceMs \<ms>` | Nastaví prodlevu pro potlačení zákmitů při změnách souborů v milisekundách (výchozí: `1000`). |
| `--verbose` / `--debug` | Zobrazí podrobné protokoly sestavení, požadavky synchronizace a trasování chyb. |
## Co můžete vytvořit
Aplikace se skládají z **entit** — každá je definována jako soubor TypeScriptu s jediným `export default`:
| Entita | K čemu slouží |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **Objekty a pole** | Vlastní datové modely (pohlednice, faktura apod.) s typovanými poli |
| **Logické funkce** | Serverový TypeScript spouštěný HTTP trasami, plánovačem cron nebo událostmi databáze |
| **Frontendové komponenty** | Komponenty Reactu, které se vykreslují v uživatelském rozhraní Twenty (postranní panel, widgety, příkazová nabídka) |
| **Dovednosti a agenti** | Schopnosti AI — opakovaně použitelné pokyny a autonomní asistenti |
| **Pohledy a navigace** | Předkonfigurované seznamové pohledy a položky postranní nabídky |
| **Rozvržení stránek** | Vlastní stránky detailu záznamu s kartami a widgety |
Úplná reference: [Koncepty](/l/cs/developers/extend/apps/getting-started/concepts).
## Další kroky
<CardGroup cols={2}>
<Card title="Konfigurace" icon="screwdriver-wrench" href="/l/cs/developers/extend/apps/config/overview">
Identita aplikace, výchozí role, instalační i odinstalační hooky, veřejná aktiva.
</Card>
<Card title="Data" icon="database" href="/l/cs/developers/extend/apps/data/overview">
Objekty, pole a obousměrné relace.
</Card>
<Card title="Logika" icon="bolt" href="/l/cs/developers/extend/apps/logic/overview">
Logické funkce, dovednosti, agenti a připojení přes OAuth.
</Card>
<Card title="Rozvržení" icon="table-columns" href="/l/cs/developers/extend/apps/layout/overview">
Zobrazení, navigace, rozvržení stránek, frontendové komponenty.
</Card>
<Card title="Operace" icon="rocket" href="/l/cs/developers/extend/apps/operations/overview">
CLI, testování, vzdálené repozitáře, CI a publikování vaší aplikace.
</Card>
</CardGroup>

View File

@@ -0,0 +1,63 @@
---
title: Vytvoření kostry
description: Interaktivně generujte soubory entit pomocí yarn twenty dev:add – objekty, pole, zobrazení, logické funkce a další.
icon: wand-magic-sparkles
---
Místo ručního vytváření souborů entit použijte interaktivní generátor:
```bash filename="Terminal"
yarn twenty dev:add
```
Vyžádá si, abyste vybrali typ entity, provede vás požadovanými poli a poté zapíše připravený soubor s pevně daným `universalIdentifier` a správným voláním `defineEntity()`.
Můžete také předat typ entity přímo a přeskočit první dotaz:
```bash filename="Terminal"
yarn twenty dev:add object
yarn twenty dev:add logicFunction
yarn twenty dev:add frontComponent
```
## Dostupné typy entit
| Typ entity | Příkaz | Vygenerovaný soubor |
| -------------------------- | ------------------------------------------ | --------------------------------------------------------- |
| Objekt | `yarn twenty dev:add object` | `src/objects/\<name>.ts` |
| Pole | `yarn twenty dev:add field` | `src/fields/\<name>.ts` |
| Logická funkce | `yarn twenty dev:add logicFunction` | `src/logic-functions/\<name>.ts` |
| Frontendová komponenta | `yarn twenty dev:add frontComponent` | `src/front-components/\<name>.tsx` |
| Role | `yarn twenty dev:add role` | `src/roles/\<name>.ts` |
| Dovednost | `yarn twenty dev:add skill` | `src/skills/\<name>.ts` |
| Agent | `yarn twenty dev:add agent` | `src/agents/\<name>.ts` |
| Zobrazení | `yarn twenty dev:add view` | `src/views/\<name>.ts` |
| Položka navigační nabídky | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
| Rozvržení stránky | `yarn twenty dev:add pageLayout` | `src/page-layouts/\<name>.ts` |
| Karta Rozložení stránky | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/\<name>.ts` |
| Položka menu příkazů | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/\<name>.ts` |
| Pole zobrazení | `yarn twenty dev:add viewField` | `src/view-fields/\<name>.ts` |
| Poskytovatel připojení | `yarn twenty dev:add connectionProvider` | `src/connection-providers/\<name>.ts` |
| Typ aktivity na časové ose | `yarn twenty dev:add timelineActivityType` | `src/timeline-activity-types/\<name>.ts` |
## Co generátor vytváří
Každý typ entity má vlastní šablonu. Například `yarn twenty dev:add object` se zeptá na:
1. **Název (jednotné číslo)** — např. `invoice`
2. **Název (množné číslo)** — např. `invoices`
3. **Štítek (jednotné číslo)** — automaticky doplněn z názvu (např. `Invoice`)
4. **Štítek (množné číslo)** — automaticky doplněn (např. `Invoices`)
5. **Vytvořit zobrazení a položku navigace?** — pokud odpovíte ano, generátor také vytvoří odpovídající zobrazení a odkaz v postranním panelu pro nový objekt.
Ostatní typy entit mají jednodušší dotazy — většinou se ptají pouze na název.
Typ entity `field` je podrobnější: ptá se na název pole, štítek, typ (ze seznamu všech dostupných typů polí jako `TEXT`, `NUMBER`, `SELECT`, `RELATION` atd.) a `universalIdentifier` cílového objektu.
## Vlastní výstupní cesta
Pomocí příznaku `--path` umístíte vygenerovaný soubor do vlastního umístění:
```bash filename="Terminal"
yarn twenty dev:add logicFunction --path src/custom-folder
```

View File

@@ -0,0 +1,14 @@
---
title: Řešení potíží
description: Časté problémy při prvním spuštění — Docker, verze Node, Yarn, závislosti.
icon: wrench
---
* **Chyby Dockeru** — Před spuštěním `yarn twenty docker:start` se ujistěte, že Docker Desktop (nebo démon) běží. Chybová zpráva ukáže správný příkaz pro spuštění pro váš operační systém.
* **Špatná verze Node** — Potřebujete verzi 24.5+ (`engines.node: ^24.5.0`). Ověřte pomocí `node -v`.
* **Chybí Yarn 4** — Spusťte `corepack enable`.
* **Rozbité závislosti** — `rm -rf node_modules && yarn install`.
* **Chyby `twenty-sdk` po upgradu na v2.8.0** — V 2.8.0 byl přesunut z `dependencies` do `devDependencies`. Viz [Struktura projektu → Závislosti](/l/cs/developers/extend/apps/getting-started/project-structure#dependencies).
* **`twenty dev:build` upozorňuje na `twenty-client-sdk` v sekci `dependencies`** — je poskytován za běhu Twenty, takže by měl být přesunut do `devDependencies` vedle `twenty-sdk`. Viz [Struktura projektu → Závislosti](/l/cs/developers/extend/apps/getting-started/project-structure#dependencies).
Zasekli jste se? Zeptejte se na [Discordu Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).

View File

@@ -0,0 +1,118 @@
---
title: Položky příkazové nabídky
description: Zpřístupněte front komponenty jako rychlé akce a položky příkazového menu (Cmd+K) pomocí defineCommandMenuItem.
icon: terminal
---
**Položka příkazového menu** je most mezi uživatelem a [front komponentou](/l/cs/developers/extend/apps/layout/front-components). Registruje komponentu v příkazovém menu Twenty (Cmd+K) a volitelně také jako připnuté tlačítko rychlé akce v pravém horním rohu stránky.
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
label: 'Open Dashboard',
shortLabel: 'Dashboard',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
## Konfigurační pole
| Pole | Povinné | Popis |
| --------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz |
| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) |
| `frontComponentUniversalIdentifier` | Ano | `universalIdentifier` frontendové komponenty, kterou tento příkaz otevírá |
| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce |
| `icon` | Ne | **Zastaralé** — ignorováno ve prospěch ikony aplikace; pokud je nastaveno, sestavení vypíše varování |
| `isPinned` | Ne | Pokud je `true`, zobrazí příkaz jako tlačítko rychlé akce v pravém horním rohu stránky |
| `availabilityType` | Ne | Určuje, kde se příkaz zobrazuje: `'GLOBAL'` (vždy dostupné), `'GLOBAL_OBJECT_CONTEXT'` (pouze na stránkách s kontextem objektu — indexové a záznamové stránky), `'RECORD_SELECTION'` (pouze když jsou vybrány záznamy) nebo `'FALLBACK'` (zobrazeno, když neodpovídají žádné jiné příkazy) |
| `availabilityObjectUniversalIdentifier` | Ne | Omezí příkaz na stránky konkrétního typu objektu (např. pouze u záznamů Company) |
| `conditionalAvailabilityExpression` | Ne | Logický výraz, který dynamicky řídí viditelnost (viz níže) |
## Příkazy bez rozhraní
Položka příkazového menu spárovaná s [front komponentou bez rozhraní](/l/cs/developers/extend/apps/layout/front-components#headless-vs-non-headless) je idiomatický způsob, jak dodat akci na jedno kliknutí — spustit kód, přejít na stránku nebo potvrdit a provést. Stránka Front Components popisuje [SDK Command komponenty](/l/cs/developers/extend/apps/layout/front-components#sdk-command-components) (`Command`, `CommandLink`, `CommandModal`, `CommandOpenSidePanelPage`), které obsluhují pattern akce-a-unmount.
Typický průběh: bezhlavá komponenta vykreslí `<Command execute={...} />` (viz [úplný příklad](/l/cs/developers/extend/apps/layout/front-components#sdk-command-components)) a položka v nabídce příkazů na ni ukazuje:
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
## Výrazy podmíněné dostupnosti
Pole `conditionalAvailabilityExpression` vám umožní řídit viditelnost příkazu na základě aktuálního kontextu stránky. Pro sestavení výrazů importujte typované proměnné a operátory z `twenty-sdk`:
```ts src/command-menu-items/bulk-update.command-menu-item.ts
import {
defineCommandMenuItem,
objectPermissions,
everyEquals,
} from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: '...',
label: 'Bulk Update',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: '...',
conditionalAvailabilityExpression: everyEquals(
objectPermissions,
'canUpdateObjectRecords',
true,
),
});
```
<Note>
`RECORD_SELECTION` již znamená neprázdný výběr — použijte `numberOfSelectedRecords` pouze pro konkrétní počty (např. `>= 2`).
</Note>
### Kontextové proměnné
Tyto proměnné reprezentují aktuální stav stránky:
| Proměnná | Typ | Popis |
| ------------------------------ | --------- | -------------------------------------------------------------------- |
| `pageType` | `string` | Aktuální typ stránky (např. `'RecordIndexPage'`, `'RecordShowPage'`) |
| `isInSidePanel` | `boolean` | Zda je komponenta vykreslena v postranním panelu |
| `numberOfSelectedRecords` | `number` | Počet aktuálně vybraných záznamů |
| `isSelectAll` | `boolean` | Zda je aktivní "vybrat vše" |
| `selectedRecords` | `array` | Vybrané objekty záznamů |
| `favoriteRecordIds` | `array` | ID oblíbených záznamů |
| `objectPermissions` | `object` | Oprávnění pro aktuální typ objektu |
| `targetObjectReadPermissions` | `object` | Oprávnění ke čtení pro cílový objekt |
| `targetObjectWritePermissions` | `object` | Oprávnění k zápisu pro cílový objekt |
| `featureFlags` | `object` | Aktivní příznaky funkcí |
| `objectMetadataItem` | `object` | Metadata aktuálního typu objektu |
| `hasAnySoftDeleteFilterOnView` | `boolean` | Zda má aktuální zobrazení filtr soft-delete |
### Operátory
Kombinujte proměnné do logických výrazů:
| Operátor | Popis |
| ----------------------------------- | ------------------------------------------------------------------------------ |
| `isDefined(value)` | `true`, pokud hodnota není null/undefined |
| `isNonEmptyString(value)` | `true`, pokud je hodnota neprázdným řetězcem |
| `includes(array, value)` | `true`, pokud pole obsahuje danou hodnotu |
| `includesEvery(array, prop, value)` | `true`, pokud vlastnost každé položky zahrnuje danou hodnotu |
| `every(array, prop)` | `true`, pokud je vlastnost u každé položky pravdivá (truthy) |
| `everyDefined(array, prop)` | `true`, pokud je vlastnost definována u každé položky |
| `everyEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě u každé položky |
| `some(array, prop)` | `true`, pokud je vlastnost pravdivá (truthy) alespoň u jedné položky |
| `someDefined(array, prop)` | `true`, pokud je vlastnost definována alespoň u jedné položky |
| `someEquals(array, prop, value)` | `true`, pokud se vlastnost rovná hodnotě alespoň u jedné položky |
| `someNonEmptyString(array, prop)` | `true`, pokud má vlastnost alespoň u jedné položky hodnotu neprázdného řetězce |
| `none(array, prop)` | `true`, pokud je vlastnost u všech položek nepravdivá (falsy) |
| `noneDefined(array, prop)` | `true`, pokud je vlastnost u všech položek nedefinovaná |
| `noneEquals(array, prop, value)` | `true`, pokud se vlastnost nerovná hodnotě u žádné položky |

View File

@@ -0,0 +1,849 @@
---
title: Frontendové komponenty
description: Vytvářejte komponenty Reactu, které se vykreslují uvnitř uživatelského rozhraní Twenty se sandboxovou izolací.
icon: window-maximize
---
Frontendové komponenty jsou React komponenty, které se vykreslují přímo v uživatelském rozhraní Twenty. Běží v **izolovaném Web Workeru** s využitím Remote DOM — váš kód se spouští uvnitř sandboxovaného iframe s nejasným původem (opaque-origin), ale jeho UI se stále vykresluje nativně na stránce, místo aby bylo omezené na tento iframe.
<Warning>
Komponenty Front jsou stále aktivně vyvíjeny. Váš kód běží nad částečným DOMem, nikoli nad skutečnou stránkou prohlížeče, takže pokročilé způsoby použití mohou selhávat, často bez zjevných chyb. Viz [Aktuální omezení](#current-limitations).
</Warning>
## Kde lze použít frontendové komponenty
Frontendové komponenty se mohou vykreslovat na třech místech v rámci Twenty:
* **Postranní panel** — Frontendové komponenty, které nejsou headless, se otevírají v pravém postranním panelu. Toto je výchozí chování, když je frontendová komponenta vyvolána z příkazového menu.
* **Widgety (nástěnky a stránky záznamů)** — front komponenty lze vkládat jako widgety do [rozložení stránky](/l/cs/developers/extend/apps/layout/page-layouts). Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget frontendové komponenty.
* **Nastavení aplikace** — Definovaná pomocí [`defineSettingsFrontComponent()`](#custom-settings-component), frontendová komponenta se vykreslí jako sekce na kartě **Settings** (Nastavení) aplikace, místo výchozího uživatelského rozhraní pro konfiguraci proměnných.
Samotná frontendová komponenta není z uživatelského rozhraní dostupná — je potřeba ji zpřístupnit. Tři způsoby, jak to udělat, jsou:
* **Spárujte ji s [položkou příkazové nabídky](/l/cs/developers/extend/apps/layout/command-menu-items)** — zaregistruje ji v příkazové nabídce (Cmd+K) a volitelně také jako připnutou rychlou akci.
* **Vložte ji jako widget do [rozložení stránky](/l/cs/developers/extend/apps/layout/page-layouts)** — umístí ji na detailní stránku záznamu nebo na nástěnku.
* **Definujte ji pomocí [`defineSettingsFrontComponent()`](#custom-settings-component)** — vykreslí ji jako sekci na kartě **Settings** (Nastavení) aplikace, místo výchozího uživatelského rozhraní pro konfiguraci proměnných.
## Základní příklad
Nejrychlejší způsob, jak vidět front komponentu v akci, je spárovat ji s [`defineCommandMenuItem`](/l/cs/developers/extend/apps/layout/command-menu-items), aby se objevila jako tlačítko rychlé akce v pravém horním rohu stránky:
```tsx src/front-components/hello-world.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
const HelloWorld = () => {
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Hello from my app!</h1>
<p>This component renders inside Twenty.</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
name: 'hello-world',
description: 'A simple front component',
component: HelloWorld,
});
```
```ts src/command-menu-items/hello-world.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
shortLabel: 'Hello',
label: 'Hello World',
isPinned: true,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier: '74c526eb-cb68-4cf7-b05c-0dd8c288d948',
});
```
Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty apply`) se rychlá akce zobrazí v pravém horním rohu stránky:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/quick-action.png" alt="Tlačítko rychlé akce v pravém horním rohu" />
</div>
Kliknutím na něj vykreslíte komponentu přímo ve stránce.
## Konfigurační pole
| Pole | Povinné | Popis |
| --------------------- | ------- | ----------------------------------------------------------------- |
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro tuto komponentu |
| `component` | Ano | Funkce komponenty React |
| `name` | Ne | Zobrazovaný název |
| `description` | Ne | Popis toho, co komponenta dělá |
| `isHeadless` | Ne | Nastavte na `true`, pokud komponenta nemá viditelné UI (viz níže) |
## Umístění frontendové komponenty na stránku
Mimo příkazy můžete frontendovou komponentu vložit přímo na stránku záznamu přidáním jako widget v **rozvržení stránky**. Podrobnosti viz [Rozložení stránek](/l/cs/developers/extend/apps/layout/page-layouts).
## Vlastní komponenta nastavení
Chcete-li nahradit automaticky generované uživatelské rozhraní pro konfiguraci proměnných na kartě **Settings** (Nastavení) vaší aplikace vlastní komponentou, definujte ji pomocí `defineSettingsFrontComponent` místo `defineFrontComponent`. Používá stejná [konfigurační pole](#configuration-fields) (kromě `isHeadless`, který není podporován, protože komponenta nastavení vždy vykresluje viditelné uživatelské rozhraní) a zároveň označuje komponentu jako uživatelské rozhraní nastavení aplikace.
Komponenta se vykreslí jako sekce **uvnitř** karty Settings, nikoli jako náhrada celé karty. Systémem spravované sekce Twenty — automatická aktualizace, App URL a připojení — se vždy zobrazují nad ní a aplikace je nemůže přebít.
```tsx src/front-components/app-settings.tsx
import { defineSettingsFrontComponent } from 'twenty-sdk/define';
const AppSettings = () => {
return (
<div style={{ padding: '20px' }}>
<h2>My app settings</h2>
{/* render your own configuration UI here */}
</div>
);
};
export default defineSettingsFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'app-settings',
description: "Custom UI for the app's Settings tab",
component: AppSettings,
});
```
Na jednu aplikaci je povolena pouze jedna frontová komponenta nastavení; deklarace více než jedné způsobí selhání sestavení. Je-li přítomna, karta **Settings** aplikace vykreslí tuto komponentu místo výchozího uživatelského rozhraní pro konfiguraci proměnných.
## Headless vs. ne-headless
Front-endové komponenty existují ve dvou režimech vykreslování řízených volbou `isHeadless`:
**Ne-headless (výchozí)** — Komponenta vykreslí viditelné uživatelské rozhraní. Po vyvolání z menu příkazů se otevře v postranním panelu. Toto je výchozí chování, když je `isHeadless` `false` nebo když tato volba není uvedena.
**Headless (`isHeadless: true`)** — Komponenta se neviditelně inicializuje na pozadí. Neotevírá postranní panel. Headless komponenty jsou určené pro akce, které provedou logiku a poté se odpojí — například spuštění asynchronního úkolu, navigaci na stránku nebo zobrazení potvrzovacího modálního okna. Přirozeně se hodí ke komponentám SDK Command popsaným níže.
```tsx src/front-components/sync-tracker.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds, enqueueSnackbar } from 'twenty-sdk/front-component';
import { useEffect } from 'react';
const SyncTracker = () => {
const [recordId] = useSelectedRecordIds();
useEffect(() => {
enqueueSnackbar({ message: `Tracking record ${recordId}`, variant: 'info' });
}, [recordId]);
return null;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-tracker',
description: 'Tracks record views silently',
isHeadless: true,
component: SyncTracker,
});
```
Protože komponenta vrací `null`, Twenty přeskočí vykreslení kontejneru — v rozvržení se neobjeví žádné prázdné místo. Komponenta má však stále přístup ke všem hookům a API komunikace s hostitelem.
## Komponenty SDK Command
Balíček `twenty-sdk` poskytuje čtyři pomocné komponenty Command navržené pro headless front-endové komponenty. Každá komponenta při připojení provede akci, chyby zpracuje zobrazením oznámení ve snackbaru a po dokončení automaticky odpojí front-endovou komponentu.
Importujte je z `twenty-sdk/front-component`:
* **`Command`** — Spustí asynchronní callback přes prop `execute`.
* **`CommandLink`** — Naviguje na cestu v aplikaci. Props: `to`, `params`, `queryParams`, `options`.
* **`CommandModal`** — Otevře potvrzovací modální okno. Pokud uživatel potvrdí, provede callback `execute`. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`.
* **`CommandOpenSidePanelPage`** — Otevře stránku postranního panelu. Props závisí na `page` — např. `ViewRecord` bere `recordId` + `objectNameSingular` (plus volitelné id `tab` pro otevření záznamu na konkrétní záložce), ostatní stránky berou `pageTitle` + `pageIcon`.
Zde je kompletní příklad headless front-endové komponenty, která pomocí `Command` spouští akci z menu příkazů:
```tsx src/front-components/run-action.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const RunAction = () => {
const execute = async () => {
const client = new CoreApiClient();
await client.mutation({
createTask: {
__args: { data: { title: 'Created by my app' } },
id: true,
},
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
name: 'run-action',
description: 'Creates a task from the command menu',
component: RunAction,
isHeadless: true,
});
```
```ts src/command-menu-items/run-action.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
label: 'Run my action',
frontComponentUniversalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234',
});
```
A příklad s použitím `CommandModal` k vyžádání potvrzení před provedením:
```tsx src/front-components/delete-draft.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { CommandModal } from 'twenty-sdk/front-component';
const DeleteDraft = () => {
const execute = async () => {
// perform the deletion
};
return (
<CommandModal
title="Delete draft?"
subtitle="This action cannot be undone."
execute={execute}
confirmButtonText="Delete"
confirmButtonAccent="danger"
/>
);
};
export default defineFrontComponent({
universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456',
name: 'delete-draft',
description: 'Deletes a draft with confirmation',
component: DeleteDraft,
isHeadless: true,
});
```
A zde je příklad použití `CommandOpenSidePanelPage` k otevření aktuálního záznamu v postranním panelu na konkrétní záložce. `tab` je ID záložky rozvržení stránky (výchozí rozvržení používají ID jako `company-tab-emails` nebo `company-tab-timeline`; vlastní rozvržení používají vlastní ID záložky). Pokud ID v rozvržení záznamu neexistuje, místo toho se otevře výchozí záložka:
```tsx src/front-components/open-company-emails.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
CommandOpenSidePanelPage,
SidePanelPages,
useSelectedRecordIds,
} from 'twenty-sdk/front-component';
const OpenCompanyEmails = () => {
const selectedRecordIds = useSelectedRecordIds();
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
if (!recordId) {
return null;
}
return (
<CommandOpenSidePanelPage
page={SidePanelPages.ViewRecord}
recordId={recordId}
objectNameSingular="company"
tab="company-tab-emails"
resetNavigationStack={false}
/>
);
};
export default defineFrontComponent({
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
name: 'open-company-emails',
description: 'Opens the current company on its Emails tab',
component: OpenCompanyEmails,
isHeadless: true,
});
```
## Volání logické funkce
Front komponenty běží v prohlížeči v sandboxovaném Web Workeru uvnitř iframe s nejasným původem (opaque-origin), zatímco [logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) běží na serveru. Neexistuje mezi nimi žádné přímé volání v rámci jednoho procesu — místo toho se front komponenta k logické funkci připojuje přes HTTP.
Logická funkce deklarovaná pomocí `httpRouteTriggerSettings` je přes HTTP dostupná na své cestě (route path). `RestApiClient` považuje cesty začínající na `/s/` za aplikační trasy, převede je na URL, ze které jsou vaše funkce poskytovány, a autentizuje je pomocí `TWENTY_APP_ACCESS_TOKEN`.
> **V Twenty Cloud jsou logické funkce spouštěné přes HTTP poskytovány na vyhrazené doméně pro každý workspace** na adrese `https://\<your-workspace-subdomain>.withtwenty.com\<path>`. Pro externí volající zkopírujte přesnou URL z nastavení funkce **HTTP trigger** nebo z karty **Settings** aplikace.
Headless front komponenta může volání spustit při mountu přes komponentu `Command` a poté se automaticky odmountovat:
```tsx src/front-components/sync-prs.tsx
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { Command } from 'twenty-sdk/front-component';
const SyncPrs = () => {
const execute = async () => {
await new RestApiClient().post('/s/github/fetch-prs', {
owner: 'twentyhq',
repo: 'twenty',
});
};
return <Command execute={execute} />;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'sync-prs',
description: 'Triggers the fetch-prs logic function',
isHeadless: true,
component: SyncPrs,
});
```
Cesta předaná `RestApiClient` je `httpRouteTriggerSettings.path` logické funkce s předponou `/s`. Ponechte `isAuthRequired: true`; `TWENTY_APP_ACCESS_TOKEN`, který Twenty vygeneruje pro vaši komponentu, požadavek autentizuje:
```ts src/logic-functions/fetch-prs.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
const { owner, repo } = (event.body ?? {}) as { owner: string; repo: string };
// ...fetch from GitHub and persist records...
return { ok: true };
};
export default defineLogicFunction({
universalIdentifier: '...',
name: 'fetch-prs',
handler,
httpRouteTriggerSettings: {
path: '/github/fetch-prs',
httpMethod: 'POST',
isAuthRequired: true,
},
});
```
<Note>
`TWENTY_APP_ACCESS_TOKEN` je vložen automaticky — viz [Proměnné aplikace](#application-variables). Protože tajné proměnné aplikace nejsou nikdy vystaveny front komponentám, ponechte API klíče a další citlivou logiku v logické funkci, ne ve front komponentě.
</Note>
### Volání Twenty REST API
Pro volání aplikačních HTTP tras nebo čtení a zápis záznamů Twenty z front komponenty použijte `RestApiClient` z `twenty-client-sdk/rest`. Odesílá cesty `/s/...` na základní URL funkcí vašeho workspace a všechny ostatní cesty, včetně `/rest/...`, na `TWENTY_API_URL`.
Vždy jedná jako osoba, která si stránku prohlíží. `runAs: 'application'` je pouze volba funkce logiky: komponenta nikdy neobdrží vlastní token vaší aplikace, takže požadavek na něj zde vyvolá chybu. Práci, která vyžaduje vlastní přístup aplikace, vložte do funkce logiky a místo toho volejte ji.
| Metoda | Popis |
| --------------------------------- | -------------------------------------------------------------------- |
| `get(path, options?)` | Odešle požadavek `GET` |
| `post(path, body?, options?)` | Odešle požadavek `POST` |
| `put(path, body?, options?)` | Odešle požadavek `PUT` |
| `patch(path, body?, options?)` | Odešle požadavek `PATCH` |
| `delete(path, options?)` | Odešle požadavek `DELETE` |
| `request(method, path, options?)` | Obecný požadavek s libovolnou metodou HTTP |
| `resolveUrl(path, options?)` | Převede cestu na její úplnou URL bez odeslání požadavku (pro odkazy) |
`options` přijímá `headers`, `query` (záznam parametrů dotazovacího řetězce; hodnoty typu nullish jsou vynechány) a `AbortSignal` prostřednictvím `signal`. Objekt `body`, který není typu `FormData`, je automaticky serializován do JSON. Při `401` klient jednou obnoví přístupový token prostřednictvím hostitele a požadavek znovu odešle.
Základní URL a token jsou ve výchozím nastavení odvozeny z prostředí. Podle potřeby předávejte konstruktoru přepsané hodnoty — například v testech:
```ts
const client = new RestApiClient({
baseUrl: 'https://myworkspace.twenty.com',
token: 'my-token',
});
```
Neúspěšné požadavky vyvolají `RestApiClientError`, který zpřístupňuje `status`, `statusText`, `url` a parsované `body`:
```tsx
import { RestApiClient, RestApiClientError } from 'twenty-client-sdk/rest';
const client = new RestApiClient();
try {
const people = await client.get('/rest/people', {
query: { limit: 10 },
});
} catch (error) {
if (error instanceof RestApiClientError) {
console.error(error.status, error.body);
}
}
```
## Přístup k běhovému kontextu
Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli, záznamu a instanci komponenty:
```tsx src/front-components/record-info.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import {
useUserId,
useSelectedRecordIds,
useFrontComponentId,
} from 'twenty-sdk/front-component';
const RecordInfo = () => {
const userId = useUserId();
const [recordId] = useSelectedRecordIds();
const componentId = useFrontComponentId();
return (
<div>
<p>User: {userId}</p>
<p>Record: {recordId ?? 'No record context'}</p>
<p>Component: {componentId}</p>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f23456789012',
name: 'record-info',
component: RecordInfo,
});
```
Dostupné hooky:
| Hook | Vrací | Popis |
| --------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------ |
| `useUserId()` | `string` nebo `null` | ID aktuálního uživatele |
| `useSelectedRecordIds()` | `string[]` | Všechna vybraná ID záznamů (prázdné pole, pokud není nic vybráno) |
| `useRecordId()` | `string` nebo `null` | **Zastaralé.** Použijte místo toho `useSelectedRecordIds()` |
| `useFrontComponentId()` | `string` | ID této instance komponenty |
| `useTimelineActivityId()` | `string` nebo `null` | ID aktuální aktivity časové osy při vykreslování vlastního řádku časové osy |
| `useColorScheme()` | `'light'` nebo `'dark'` | Aktivní barevné schéma uživatelského rozhraní hostitele (`System` je již vyhodnocen) |
| `useFrontComponentExecutionContext(selector)` | různé | Přístup k úplnému kontextu běhu pomocí selektorové funkce |
## Aplikační proměnné
Aplikační proměnné definované v [`defineApplication()`](/l/cs/developers/extend/apps/config/application) s `isSecret: false` jsou k dispozici ve front-endových komponentách prostřednictvím pomocné funkce `getApplicationVariable`:
```tsx src/front-components/greeting.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getApplicationVariable } from 'twenty-sdk/front-component';
const Greeting = () => {
const recipientName = getApplicationVariable('DEFAULT_RECIPIENT_NAME') ?? 'World';
return <p>Hello, {recipientName}!</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'greeting',
component: Greeting,
});
```
<Warning>
Tajné proměnné (`isSecret: true`) **nejsou** zpřístupněny front-endovým komponentám. Jsou k dispozici pouze v [logických funkcích](/l/cs/developers/extend/apps/logic/logic-functions), které běží na straně serveru. Tím se zabrání odesílání citlivých hodnot, jako jsou API klíče, do prohlížeče.
</Warning>
`getApplicationVariable` vždy vrací **string** (nebo `undefined`), bez ohledu na deklarovaný `type` proměnné. Řetězec je serializován konzistentně podle typu (logické hodnoty jako `"true"` / `"false"`, čísla jako desetinné řetězce, pole / objekty jako JSON), ve stejném formátu, jaký používá `process.env` v logických funkcích — zpracujte jej sami (`Number(...)`, `JSON.parse(...)`, `=== 'true'`). Viz [Typy proměnných](/l/cs/developers/extend/apps/config/application#variable-types).
Následující systémové proměnné jsou vždy dostupné přes `process.env`:
| Proměnná | Popis |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TWENTY_API_URL` | Základní URL Twenty core API |
| `TWENTY_APP_ACCESS_TOKEN` | Krátkodobý token s rozsahem oprávnění omezeným na roli přihlášené osoby v průniku s rolí vaší aplikace, takže komponenta nikdy nemůže dělat víc než osoba, která si ji prohlíží |
### `TWENTY_FUNCTIONS_URL`
Twenty také vkládá `TWENTY_FUNCTIONS_URL` do front komponent a logických funkcí: základní URL, ze které jsou poskytovány vaše logické funkce spouštěné přes HTTP.
Existuje proto, že tato URL není vždy samotný server Twenty. V Twenty Cloud jsou aplikační trasy poskytovány na vyhrazené doméně pro každý workspace (`https://\<your-workspace-subdomain>.withtwenty.com`, nebo hlavní veřejná doména aplikace, pokud je nakonfigurována), aby odpovědi vytvořené aplikací běžely na odděleném původu, a nikoli na původu aplikace Twenty. Self-hostované a lokální instance poskytují aplikační trasy pod předponou `/s` přímo na serveru a proměnnou nemusí vůbec nastavovat. Protože se základní URL liší podle workspace a instance, váš kód ji nemůže napevno zakódovat — server správnou hodnotu vloží za běhu.
Jen zřídka ji potřebujete číst přímo. Volání svých tras provádějte přes `RestApiClient` s cestou s předponou `/s/` a klient za vás URL vyřeší: odstraní předponu `/s` a zacílí na `TWENTY_FUNCTIONS_URL`, přičemž pokud proměnná není nastavena, použije jako zálohu `\<TWENTY_API_URL>/s`. Použijte `resolveUrl('/s/\<path>')` pro získání absolutní URL bez odeslání požadavku, např. pro odkaz. Proměnnou čtěte přímo pouze při ručním sestavování URL:
```ts
const routeUrl = `${process.env.TWENTY_FUNCTIONS_URL || `${process.env.TWENTY_API_URL}/s`}/documents/generate`;
```
## API komunikace s hostitelem
Frontendové komponenty mohou pomocí funkcí z `twenty-sdk` vyvolávat navigaci, modály a oznámení:
| Funkce | Popis |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `navigate(to, params?, queryParams?, options?)` | Přejít na stránku v aplikaci |
| `openSidePanelPage(params)` | Otevřít postranní panel |
| `closeSidePanel()` | Zavřít postranní panel |
| `openCommandConfirmationModal(params)` | Zobrazit potvrzovací dialog |
| `enqueueSnackbar(params)` | Zobrazit oznámení typu toast |
| `unmountFrontComponent()` | Odpojit komponentu |
| `updateProgress(progress)` | Aktualizovat indikátor průběhu |
| `uploadFile(file, { fieldMetadataId, fileName? })` | Nahraje `Blob` do pole FILES; vrátí `{ status: 'uploaded', file: { fileId, path, url, size, mimeType } }` nebo `{ status: 'failed', reason: 'invalid-params' \| 'upload-failed' }` |
Zde je příklad, který používá hostitelské API k zobrazení snackbaru a zavření postranního panelu po dokončení akce:
```tsx src/front-components/archive-record.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { enqueueSnackbar, closeSidePanel, useSelectedRecordIds } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const ArchiveRecord = () => {
const [recordId] = useSelectedRecordIds();
const handleArchive = async () => {
const client = new CoreApiClient();
await client.mutation({
updateTask: {
__args: { id: recordId, data: { status: 'ARCHIVED' } },
id: true,
},
});
await enqueueSnackbar({
message: 'Record archived',
variant: 'success',
});
await closeSidePanel();
};
return (
<div style={{ padding: '20px' }}>
<p>Archive this record?</p>
<button onClick={handleArchive}>Archive</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678',
name: 'archive-record',
description: 'Archives the current record',
component: ArchiveRecord,
});
```
### Úložiště
`localStorage` a `sessionStorage` fungují stejně jako na běžné stránce, se standardním synchronním API. Vaše klíče jsou omezené na instalaci vaší aplikace a přihlášeného uživatele: žádná jiná aplikace je nemůže číst a jiný uživatel, který se přihlásí ve stejném prohlížeči, začne s prázdným úložištěm. Hodnoty zapsané do `localStorage` zůstávají v zařízení i po opětovném načtení; `sessionStorage` trvá po dobu relace prohlížeče.
```tsx src/front-components/note-draft.tsx
import { useState } from 'react';
import { defineFrontComponent } from 'twenty-sdk/define';
const NoteDraft = () => {
const [draft, setDraft] = useState(() => localStorage.getItem('draft') ?? '');
const handleChange = (nextDraft: string) => {
setDraft(nextDraft);
localStorage.setItem('draft', nextDraft);
};
return (
<textarea value={draft} onChange={(event) => handleChange(event.target.value)} />
);
};
export default defineFrontComponent({
universalIdentifier: 'd0e1f2a3-b4c5-6789-def0-890123456789',
name: 'note-draft',
description: 'Keeps an unsaved note on this device',
component: NoteDraft,
});
```
Twenty ukládá hodnoty jménem vaší aplikace, takže zápis se provede lokálně okamžitě a na pozadí se uloží. Operace čtení nikdy nečekají na hostitele. Nic se nesynchronizuje: hodnoty uživatele se s ním nepřenášejí do jiného prohlížeče nebo počítače, takže pro cokoli, co musí přežít změnu zařízení, použijte [úložiště klíč–hodnota](/l/cs/developers/extend/apps/logic/key-value-store) logické funkce.
Zápisy mají limity a všechny limity počítají znaky místo bajtů: klíče mohou mít nejvýše 512 znaků, jedna hodnota nejvýše 262 144 znaků a každé úložiště nejvýše 1 048 576 znaků na aplikaci a uživatele. Zápis, který překročí některý limit, vyvolá `QuotaExceededError`, podobně jako rozhraní API prohlížeče.
### Práce s více záznamy
Použijte `useSelectedRecordIds()` pro zpracování více vybraných záznamů. To je užitečné pro hromadné operace:
```tsx src/front-components/bulk-export.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
import { CoreApiClient } from 'twenty-client-sdk/core';
const BulkExport = () => {
const selectedRecordIds = useSelectedRecordIds();
const handleExport = async () => {
const client = new CoreApiClient();
for (const recordId of selectedRecordIds) {
await client.mutation({
updateTask: {
__args: { id: recordId, data: { exported: true } },
id: true,
},
});
}
await enqueueSnackbar({
message: `Exported ${selectedRecordIds.length} records`,
variant: 'success',
});
await closeSidePanel();
};
return (
<div style={{ padding: '20px' }}>
<p>Export {selectedRecordIds.length} selected record(s)?</p>
<button onClick={handleExport}>Export</button>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
name: 'bulk-export',
description: 'Export selected records',
component: BulkExport,
});
```
Zobrazte ji pomocí [položky příkazové nabídky](/l/cs/developers/extend/apps/layout/command-menu-items) omezené na výběry záznamů:
```ts src/command-menu-items/bulk-export.command-menu-item.ts
import { defineCommandMenuItem } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678902',
label: 'Bulk Export',
availabilityType: 'RECORD_SELECTION',
frontComponentUniversalIdentifier: 'd0e1f2a3-b4c5-6789-defa-012345678901',
});
```
## Veřejné soubory
Frontendové komponenty mohou přistupovat k souborům ze složky aplikace `public/` pomocí `getPublicAssetUrl`:
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { getPublicAssetUrl } from 'twenty-sdk/utils';
const Logo = () => <img src={getPublicAssetUrl('logo.png')} alt="Logo" />;
export default defineFrontComponent({
universalIdentifier: '...',
name: 'logo',
component: Logo,
});
```
Podrobnosti viz [sekci veřejných souborů](/l/cs/developers/extend/apps/config/public-assets).
## Sdílení závislostí mezi front komponentami
Ve výchozím nastavení si každá front komponenta přibalí vlastní kopii knihoven, které importuje, takže aplikace s pěti komponentami dodává React pětkrát. Deklarujte sdílené závislosti v `package.json` vaší aplikace, aby se tyto knihovny sestavily jen jednou a každá komponenta aplikace je načítala z jednoho společného souboru v mezipaměti:
```json package.json
{
"frontComponentSharedDependencies": ["react", "react-dom/client", "twenty-ui/input"]
}
```
Každá komponenta pak importuje své závislosti úplně stejně jako dříve — ve vašem komponentním kódu se nic nemění:
```tsx src/front-components/counter.tsx
import { useState } from 'react';
import { defineFrontComponent } from 'twenty-sdk/define';
const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'counter',
component: Counter,
});
```
Několik věcí, které je dobré vědět:
* **Jeden sdílený balík závislostí na aplikaci.** Balík je sestaven z vlastních závislostí vaší aplikace, takže máte plnou kontrolu nad verzemi, které dodáváte.
* **Uveďte přesné specifikátory, které importujete.** `twenty-ui/input` a `twenty-ui/display` jsou dvě položky; samotný název balíčku nepokrývá jeho podcesty. Uvedení `react` automaticky pokrývá `react/jsx-runtime`.
* **Sdílejte `react-dom/client` spolu s `react`.** Každá komponenta renderuje přes `createRoot`, takže pokud ho vynecháte, každá komponenta si stále přibalí vlastní React DOM.
* **Balík je cachovaný.** Je poskytován pod URL s hashovaným obsahem s dlouhodobou neměnnou mezipamětí, takže se stáhne jednou a znovu používá ve všech komponentách aplikace, dokud se některá z jeho závislostí nezmění.
* **Komponenty, které neimportují žádný ze sdílených balíčků, ho nikdy nestáhnou.**
## Stylování
Frontendové komponenty podporují více přístupů ke stylování. Můžete použít:
* **Inline styly** — `style={{ color: 'red' }}`
* **Komponenty Twenty UI** — vlastní knihovna komponent Twenty; podívejte se níže na [Používání komponent Twenty UI](#using-twenty-ui-components)
* **Emotion** — CSS-in-JS s `@emotion/react`
* **Styled-components** — vzory `styled.div`
* **Tailwind CSS** — utilitní třídy
* **Jakákoli CSS-in-JS knihovna** kompatibilní s Reactem
## Používání komponent Twenty UI
Twenty dodává svou knihovnu komponent jako balíček [`twenty-ui`](https://www.npmjs.com/package/twenty-ui/v/1.0.0-alpha.1). Frontendové komponenty jej mohou používat pro tlačítka, tagy, stavové štítky, čipy, avatary, ikony, typografii a tokeny motivu, které se automaticky přizpůsobují světlému a tmavému motivu pracovního prostoru.
### Instalace
Přidejte balíček do své aplikace, připnutý k verzi, se kterou je dodána vaše instance Twenty:
```bash
yarn add twenty-ui@1.0.0-alpha.1
```
`twenty-ui` je zabalen do vaší frontendové komponenty při sestavení, takže stačí, aby byl závislostí vaší aplikace — za běhu není třeba nic konfigurovat.
### Import komponent
Importujte z odpovídající podcesty místo z kořene balíčku, aby se do vašeho bundlu dostaly jen komponenty, které používáte:
| Podcesta | Co exportuje |
| --------------------------- | ------------------------------------------------ |
| `twenty-ui/input` | `Button` a formulářové vstupy |
| `twenty-ui/data-display` | `Tag`, `Status`, `Chip`, `Avatar` a další |
| `twenty-ui/feedback` | `Callout`, `Banner`, `Info` a další |
| `twenty-ui/typography` | `H1Title`, `H2Title`, `H3Title`, `Label` a další |
| `twenty-ui/icon` | Komponenty `Icon*` (např. `IconCheck`) |
| `twenty-ui/theme-constants` | `ThemeProvider`, `themeCssVariables` |
```tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { Status, Tag } from 'twenty-ui/data-display';
import { Button } from 'twenty-ui/input';
const StyledWidget = () => {
return (
<div style={{ padding: '16px', display: 'flex', gap: '8px' }}>
<Button title="Click me" onClick={() => alert('Clicked!')} />
<Tag text="Active" color="green" />
<Status color="green" text="Online" />
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-567890123456',
name: 'styled-widget',
component: StyledWidget,
});
```
### Ikony
Importujte jednotlivé ikony z `twenty-ui/icon`:
```tsx
import { IconBox, IconCheck } from 'twenty-ui/icon';
```
Každá pojmenovaná ikona je odstraňována tree-shakingem, takže import několika málo ikon přidá do vašeho bundlu jen minimum navíc. Vyhněte se `IconsProvider`, `useIcons` a `iconsState` — natáhnou celou sadu ikon Tabler (několik MB).
### Témování a tokeny motivu
Komponenty Twenty UI se automaticky přizpůsobí světlému a tmavému motivu pracovního prostoru — renderer použije na hostiteli aktivní barevné schéma a komponenty podle něj odvodí své barvy.
Chcete-li ve svých vlastních inline stylech používat stejné design tokeny, zavolejte hook `useTheme()`. Vrací tokeny motivu Twenty (odsazení, barvy, poloměry, písma) napojené na aktivní motiv, aniž by bylo potřeba v komponentě nastavovat `ThemeProvider`:
```tsx
import { useTheme } from 'twenty-ui/theme-constants';
const Card = () => {
const theme = useTheme();
return (
<div
style={{
padding: theme.spacing[4],
background: theme.background.secondary,
color: theme.font.color.primary,
}}
>
Themed card
</div>
);
};
```
Protože `useTheme()` je hook, čtete tokeny uvnitř těla komponenty, takže hodnoty vždy odrážejí aktuální motiv. Stejná mapa tokenů je také exportována jako konstanta `themeCssVariables`, ale ve frontendových komponentách preferujte `useTheme()` — modulová konstanta, která dereferencuje `themeCssVariables`, může být během extrakce manifestu aplikace nedefinovaná.
Chcete-li se explicitně větvit podle aktivního schématu, načtěte jej pomocí `useColorScheme()` z `twenty-sdk/front-component`, která vrací `'light'` nebo `'dark'`.
## Aktuální omezení
Komponenty Front jsou aktivně vyvíjeny. Renderování, stylování, obsluha událostí, měření prvků a úložiště prohlížeče fungují dobře. Cokoli, co sahá *mimo* tyto oblasti (volání metody DOM na refu, sledování změn velikosti prvku, vytváření portálu mimo váš strom) dnes chybí nebo je nekompletní a většinou selhává tiše: bez vyvolání výjimky a bez chyby TypeScriptu, protože kostra je typovaná proti plnému DOMu prohlížeče.
Pokud vás něco z toho blokuje, [vytvořte issue](https://github.com/twentyhq/twenty/issues/new/choose), aby to bylo upřednostněno.
### Rozvržení a měření
Prvky se mohou měřit samy: hostitel zrcadlí geometrii do sandboxu, takže čtení jsou obsloužena lokálně, ale mohou být zastaralá až o jeden snímek, a první čtení nikdy dříve neměřeného prvku vrátí nuly. Po zápisu znovu přečtěte v callbacku `requestAnimationFrame` nebo v efektu.
| API | Co se stane |
| ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `getBoundingClientRect()`, `offsetWidth`, `clientWidth`, `scrollWidth`, `offsetTop`, ... | Funguje, z pohledu zrcadla; přiřazení `scrollTop` / `scrollLeft` nic neudělá (no-op) |
| `getClientRects()`, `window.matchMedia()` | Vyvolá výjimku |
| `window.innerWidth`, `innerHeight`, `devicePixelRatio`, `scrollX`, `scrollY` | Funguje, hlásí **viewport prohlížeče**; vlastní velikost vašeho widgetu je `document.body.clientWidth` / `clientHeight` |
| `window.getComputedStyle()` | Vrací pouze inline styly prvku, nikdy hostem vypočtenou kaskádu |
| `ResizeObserver`, `IntersectionObserver` | `ReferenceError` (ochrany pomocí `typeof` fungují) |
| `MutationObserver` | Funguje, včetně `subtree`, `attributeFilter`, starých hodnot a `takeRecords()` |
Pozicování pomocí `getBoundingClientRect` nyní funguje, ale vše, co sleduje změny velikosti přes `ResizeObserver` (recharts `ResponsiveContainer`, `autoUpdate` z Floating UI), stále nefunguje. Stejně dávejte přednost CSS pro rozvržení: váš stylesheet se dostane ke skutečné stránce, takže flexbox, grid, `aspect-ratio`, `clamp()` a `@container` se chovají normálně, bez zpoždění o snímek.
<Note>
`requestAnimationFrame`, `fetch`, `setTimeout` a `queueMicrotask` fungují bez prefixu `window.`. Pouze `window.requestAnimationFrame(...)` a podobné volání vyvolají výjimku.
</Note>
### Přístup k DOMu
`ref` vám dává sandboxový prvek, ne `HTMLElement`.
| Co napíšete | Co se stane | Co použít místo toho |
| ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ref.current.focus()`, `.click()`, `.select()`, `.setSelectionRange()`, `.scrollIntoView()`, `video.play()` | Vyvolá výjimku | Řízené komponenty; čtěte hodnoty z `event.target` |
| `element.classList.add(...)` | Vyvolá výjimku (`classList` je `undefined`) | Sestavte si řetězec `className` sami |
| `document.createTreeWalker()` | Vyvolá výjimku | `querySelector()` / `querySelectorAll()` a `getElementById()` fungují; `getElementsByClassName()` také funguje, ale vrací statickou kolekci, ne živý `HTMLCollection` |
| `document.activeElement` | Vždy `undefined` | Sledujte fokus pomocí `onFocus` / `onBlur` |
| `canvas` | Nerenderuje nic, žádná chyba | SVG, nebo kreslit mimo obrazovku a výsledek zobrazit v prvku `img` |
| `createPortal(node, document.body)` | Nerenderuje nic, zatímco `isConnected` hlásí úspěch | Překryvné prvky inline pomocí `position: absolute`, nebo knihovně předávejte vlastní kontejnerový prvek |
Kvůli této mezeře v portálu popovery Radix, Headless UI, MUI a react-select ve výchozím stavu nic nevyrenderují. Většina z nich přijímá prop pro kontejner; nasměrujte ho na prvek, který jste vyrenderovali.
### Události
Události myši, ukazatele, dotyku, přetažení, klávesnice, fokusu, `input`/`change`/`submit`, `scroll`/`wheel`/`contextmenu` a `animationend`/`transitionend` se předávají hostiteli, a navíc několik dalších podle typu prvku: `load`/`error` na `img`, schránka a skládání (composition) na `input`/`textarea`, média na `video`/`audio`, `toggle` na `details`/`dialog`. Cokoli dalšího (`onAuxClick`, `onSelect`, `onInvalid`, `onReset`, `onAnimationStart`, zachycení ukazatele (pointer capture), `onLoad` na `img`) je bez varování zahazováno.
`document.addEventListener()` a `window.addEventListener()` se zaregistrují bez chyby, ale nikdy se nespustí, což je důvod, proč se přetažení zastaví, jakmile ukazatel opustí prvek, na kterém začalo. Ani `event.preventDefault()` se nepřenáší; odeslání formuláře, `dragover`/`drop` a kliknutí na odkazy jsou už za vás ošetřené.
### Atributy a stylování
Každý prvek předává své vlastní vlastnosti do hostitelského DOM (`href` na `a`, `src`/`alt` na `img`, `value`/`placeholder`/`disabled` na `input` atd.), a navíc má každý prvek společnou sadu: `id`, `className`, `style`, `title`, `tabIndex`, `role`, `draggable` a jakýkoli atribut `aria-*` / `data-*` (s pomlčkou; `ariaLabel` je proto ignorována). Cokoli mimo tento rozsah je tiše zahazováno, takže vlastní stav vyjádřete jako `data-*`.
Komponentové CSS, ať už z `import './styles.css'`, CSS-in-JS nebo z prvku `style`, je do `head` hostitelské stránky vkládáno **bez ohraničení (unscoped)**. Třídy se tak střetávají s vlastními třídami Twenty (přidávejte jim předpony a nikdy nepište holé `div { ... }` selektory) a `@media` se vztahuje k oknu prohlížeče, nikoli k vašemu widgetu (použijte `@container` s vlastním `container-type`). Inline propy `style` nejsou ovlivněny.
### Úložiště a síť
`localStorage` a `sessionStorage` poskytuje Twenty, nikoli prohlížeč: komponenta běží ve workeru v neprůhledném původu, takže hostitel ukládá hodnoty jménem vaší aplikace. Jejich rozsah a limity najdete v části [úložiště](#storage). IndexedDB, cookies, Cache API a `BroadcastChannel` jsou nadále nedostupné. Pro uchování stavu mezi zařízeními zavolejte [logickou funkci](/l/cs/developers/extend/apps/logic/logic-functions) a použijte její [úložiště klíč–hodnota](/l/cs/developers/extend/apps/logic/key-value-store).
`fetch` funguje, s výhradami:
* Volání na Twenty API a trasy (routes) vaší aplikace jsou proxyována hostitelem, proto upřednostněte [`RestApiClient`](#calling-the-twenty-rest-api). U proxyovaných volání jsou `AbortSignal` a ostatní volby `RequestInit` zahazovány a jsou podporována pouze těla typu `string` a `URLSearchParams`.
* Jiné původy opouštějí sandbox s `Origin: null`, takže API třetí strany odpoví jen tehdy, pokud posílá `Access-Control-Allow-Origin: *`. Místo toho jej volejte z logické funkce.
* `fetch('/rest/people')` se nikdy nespáruje s Twenty API, protože sandbox nemá URL stránky, podle které by vyhodnotil relativní cestu.
### Zachytávání médií
`navigator.mediaDevices.getUserMedia()` a `MediaRecorder` fungují uvnitř frontendových komponent díky sandboxovým polyfillům, takže standardní kód pro nahrávání běží beze změn a `MediaRecorder.isTypeSupported` udává, zda jsou podporovány běžné kombinace kontejneru/kodeku. Detailní objekty omezení `getUserMedia` jsou akceptovány, ale nejsou předávány dál — hostitel zachytává s výchozím nastavením pro požadované druhy — a napříč aplikacemi může být v daný čas aktivní pouze jedno zachytávání. Uložte zaznamenaný `Blob` pomocí hostitelské funkce `uploadFile`.
### Další omezení
* **Obsah souborů.** `input` typu `file` poskytuje vašemu handleru pouze metadata souboru, nikoli samotné bajty, takže `FileReader` není k dispozici. Chcete-li nahrát `Blob`, který váš kód již má k dispozici — např. jeden vytvořený pomocí `MediaRecorder` — použijte hostitelskou funkci `uploadFile`.
* **Payloady drag-and-drop.** Události tažení se spouštějí, ale `event.dataTransfer` je `undefined`.
* **Vestavěné moduly Node.** `fs`, `path` a `node:crypto` způsobí chybu při sestavení, takže tuto práci přesuňte do [logické funkce](/l/cs/developers/extend/apps/logic/logic-functions). Web Crypto, `fetch`, `TextEncoder` a `URL` jsou k dispozici.
* **`iframe`** je vždy znovu zasandboxováno bez `allow-same-origin`, takže vložený obsah spoléhající se na vlastní relaci se vykreslí jako odhlášený. Nemá ani `onLoad`.

View File

@@ -0,0 +1,46 @@
---
title: Položky navigační nabídky
description: Přidejte vlastní položky do postranního panelu pracovního prostoru — odkazy na uložená zobrazení nebo externí adresy URL.
icon: bars
---
**Položka navigační nabídky** je položka v levém postranním panelu. Použijte `defineNavigationMenuItem()` k přidání vlastních odkazů do postranního panelu — obvykle jeden pro každé [zobrazení](/l/cs/developers/extend/apps/layout/views), které dodáváte — nebo pro odkaz na externí adresy URL.
```ts src/navigation-menu-items/example-navigation-menu-item.ts
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from '../views/example-view';
export default defineNavigationMenuItem({
universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c',
name: 'example-navigation-menu-item',
icon: 'IconList',
color: 'blue',
position: 0,
type: NavigationMenuItemType.VIEW,
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
});
```
## Hlavní body
* `type` určuje, na co položka nabídky odkazuje. Každý typ je spárován s konkrétním identifikačním polem:
| Typ | K čemu slouží | Povinné pole |
| ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
| `NavigationMenuItemType.VIEW` | Otevře uložené zobrazení | `viewUniversalIdentifier` |
| `NavigationMenuItemType.LINK` | Otevře externí adresu URL | `link` |
| `NavigationMenuItemType.FOLDER` | Seskupuje vnořené položky pod štítkem | `name` (a podřízené položky odkazují na složku prostřednictvím `folderUniversalIdentifier`) |
| `NavigationMenuItemType.OBJECT` | Otevře výchozí indexovou stránku objektu | `targetObjectUniversalIdentifier` |
| `NavigationMenuItemType.PAGE_LAYOUT` | Otevře samostatné rozvržení stránky | `pageLayoutUniversalIdentifier` |
* `position` určuje pořadí v postranním panelu.
* Výčet také obsahuje `NavigationMenuItemType.RECORD`, používaný interně pro záznamy přidané uživatelem do oblíbených — nelze jej použít z manifestu aplikace (neexistuje žádné pole, které by odkazovalo na záznam).
* `icon` a `color` jsou volitelné a upravují, jak položka vypadá.
* `folderUniversalIdentifier` je k dispozici také na libovolné položce, aby ji bylo možné vložit do nadřazené položky typu `FOLDER`.
<Note>
**Častý problém:** vytvoření objektu bez související položky navigační nabídky způsobí, že je tento objekt z postranního panelu nedostupný. Pokud nejde o technický/interní objekt, měl by mít každý vlastní objekt položku v postranním panelu, která na něj odkazuje.
</Note>

View File

@@ -0,0 +1,59 @@
---
title: Přehled
description: Umístěte svou aplikaci do uživatelského rozhraní Twenty – položky v postranním panelu, uložená zobrazení, karty na stránce záznamu a sandboxované komponenty Reactu.
icon: table-columns
---
**Vrstva rozvržení** aplikace Twenty zahrnuje vše, co uživatel vidí: kde se aplikace zobrazuje v postranním panelu, jaká seznamová zobrazení obsahuje, jak jsou uspořádány její stránky s podrobnostmi záznamů a které vlastní komponenty Reactu se na těchto stránkách vykreslují.
```text
Sidebar Record list Record detail page
─────── ─────────── ──────────────────
[📋 My View] ────▶ ┌──────────┐ ┌─────────────────────┐
[📋 Drafts ] │ Companies│ │ Tabs: [Overview ] │
[📋 Inbox ] │ ──────── │ │ [Notes ] │
▲ │ Apple │ │ [Hello ]◀──── definePageLayoutTab
│ │ Acme │ │ │ adds a tab...
└ defineNavi- │ … │ │ ┌────────────────┐ │
gationMenu- └────▲─────┘ │ │ │ │
Item points │ │ │ React UI │◀── …with a
to a defineView │ │ │ (sandboxed in │ │ defineFrontComponent
└ defineView │ │ a Worker) │ │ widget inside
picks columns │ └────────────────┘ │
and filters └─────────────────────┘
```
## V této části
<CardGroup cols={2}>
<Card title="Zobrazení" icon="list" href="/l/cs/developers/extend/apps/layout/views">
`defineView` — uložené konfigurace seznamu: viditelné sloupce, filtry, skupiny.
</Card>
<Card title="Položky navigační nabídky" icon="bars" href="/l/cs/developers/extend/apps/layout/navigation-menu-items">
`defineNavigationMenuItem` — položky v postranním panelu odkazující na zobrazení nebo externí adresy URL.
</Card>
<Card title="Rozvržení stránek" icon="table-columns" href="/l/cs/developers/extend/apps/layout/page-layouts">
`definePageLayout` a `definePageLayoutTab` — karty a widgety na stránce s podrobnostmi záznamu.
</Card>
<Card title="Frontendové komponenty" icon="window-maximize" href="/l/cs/developers/extend/apps/layout/front-components">
`defineFrontComponent` — sandboxované komponenty Reactu, které se vykreslují uvnitř Twenty.
</Card>
<Card title="Položky příkazové nabídky" icon="terminal" href="/l/cs/developers/extend/apps/layout/command-menu-items">
`defineCommandMenuItem` — zaregistruje frontendové komponenty jako položky Cmd+K a rychlé akce.
</Card>
</CardGroup>
## Kde se aplikace zobrazuje
| Umístění | Co řídí | Entita |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| **Postranní panel** | Vlastní položka odkazující na uložené zobrazení nebo externí adresu URL | `defineNavigationMenuItem` |
| **Seznam záznamů** | Uložené nastavení pro objekt — viditelné sloupce, pořadí, filtry, skupiny | `defineView` |
| **Stránka s podrobnostmi záznamu** | Karty a widgety na stránce záznamu (vašeho vlastního objektu nebo standardního) | `definePageLayout`, `definePageLayoutTab` |
| **Uvnitř kteréhokoli z výše uvedených** | Vlastní widget Reactu — tlačítka, formuláře, přehledové panely, integrace | `defineFrontComponent` |
| **Nastavení aplikace** | Vlastní konfigurační sekce na kartě Nastavení aplikace, která nahrazuje výchozí uživatelské rozhraní proměnných | `defineSettingsFrontComponent` |
| **Příkazová nabídka (Cmd+K)** | Připnutá rychlá akce nebo skrytý příkaz | `defineCommandMenuItem` |
Frontendové komponenty běží uvnitř izolovaného Web Workeru pomocí Remote DOM — vykreslují se na stránce nativně (ne uvnitř iframe), ale nemají přímý přístup k hostitelské stránce ani DOM. Komunikace s Twenty probíhá prostřednictvím hostitelského API pro předávání zpráv.
Stále jsou v aktivním vývoji: sandbox implementuje částečný DOM, takže pokročilá použití mohou selhat. Viz [Aktuální omezení](/l/cs/developers/extend/apps/layout/front-components#current-limitations).

View File

@@ -0,0 +1,158 @@
---
title: Rozvržení stránek
description: Přizpůsobte stránky s detailem záznamu – karty, widgety a místa, kde se vykreslují frontendové komponenty – pomocí `definePageLayout` a `definePageLayoutTab`.
icon: table-columns
---
**Rozvržení stránky** určuje, jak je uspořádána stránka s detailem záznamu: které karty se zobrazí a jaké widgety obsahují. Použijte `definePageLayout()` k deklaraci rozvržení pro objekt, který vlastníte, nebo `definePageLayoutTab()` k přidání jedné karty do rozvržení, které již existuje (vašeho nebo standardního rozvržení Twenty).
| Případ použití | Entita |
| ----------------------------------------------------------------------------------------------------------- | --------------------- |
| Definujte celé rozvržení pro stránku záznamu u objektu, který vlastníte | `definePageLayout` |
| Přidejte jednu kartu do existujícího rozvržení (k rozvržení vašeho vlastního objektu nebo ke standardnímu). | `definePageLayoutTab` |
## definePageLayout
Použijte to, když vlastníte celou stránku s detailem záznamu – typicky pro vlastní objekt, který jste si definovali sami.
```ts src/page-layouts/example-record-page-layout.ts
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
export default definePageLayout({
universalIdentifier: '203aeb94-6701-46d6-9af1-be2bbcc9e134',
name: 'Example Record Page',
type: 'RECORD_PAGE',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
tabs: [
{
universalIdentifier: '6ed26b60-a51d-4ad7-86dd-1c04c7f3cac5',
title: 'Hello World',
position: 50,
icon: 'IconWorld',
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
widgets: [
{
universalIdentifier: 'aa4234e0-2e5f-4c02-a96a-573449e2351d',
title: 'Hello World',
type: 'FRONT_COMPONENT',
configuration: {
configurationType: 'FRONT_COMPONENT',
frontComponentUniversalIdentifier:
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
},
},
],
},
],
});
```
### Hlavní body
* `type` je jedním z `'RECORD_INDEX'`, `'RECORD_PAGE'`, `'DASHBOARD'` nebo `'STANDALONE_PAGE'`. Použijte `'RECORD_PAGE'` k přizpůsobení detailního zobrazení konkrétního objektu.
* `objectUniversalIdentifier` určuje, na který objekt se toto rozvržení vztahuje.
* Každá `tab` definuje sekci stránky s `title`, `position` a `layoutMode`: `VERTICAL_LIST` pro stránky záznamů a samostatné stránky, `GRID` pro řídicí panely a `CANVAS` pro jeden widget, který by měl vyplnit zobrazovací oblast karty. Karta `VERTICAL_LIST` řadí widgety svisle. Vestavěné widgety, které spravují vlastní posouvání, jako jsou časové osy, soubory, poznámky, úkoly a pracovní postupy, vyplní jednu zobrazovací oblast; pole, frontové komponenty, grafy a další widgety přizpůsobené obsahu se vykreslí ve výšce obsahu nebo nakonfigurované výšce. Karta `GRID` vždy rozvrhne své widgety jako karty v mřížce s 12 sloupci. Widget `CANVAS` nemá explicitní pozici; pokud karta plátna obsahuje více widgetů, vykreslí se ve výšce svého obsahu místo toho, aby vyplnily oblast zobrazení.
* Nastavte `layoutMode` explicitně. Pokud jej vynecháte, získáte `VERTICAL_LIST` na `STANDALONE_PAGE` a `GRID` všude jinde, což je na stránce záznamu jen zřídka to, co chcete.
* Každý `widget` uvnitř karty může vykreslit [front component](/l/cs/developers/extend/apps/layout/front-components), seznam relací nebo jiné vestavěné typy widgetů.
* Widget `FRONT_COMPONENT` může nastavit `headerCommandMenuItemUniversalIdentifiers` na seřazené pole univerzálních identifikátorů položek nabídky příkazů ze stejné aplikace. Tyto akce se v záhlaví karty widgetu zobrazují jako tlačítka s ikonami a zachovávají svou dostupnost na úrovni příkazů i kontroly oprávnění. Identifikátory musí být jedinečné a při instalaci aplikace musí být vyřešitelné.
* `position` na kartách určuje jejich pořadí. Použijte vyšší hodnoty (např. 50) pro umístění vlastních karet za vestavěné.
### Widgety polí
Widget `FIELD` vykresluje jedno pole záznamu. U relačních polí může také vložit seznam souvisejících záznamů:
```ts
{
universalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000003',
title: 'People → Opportunities',
type: 'FIELD',
configuration: {
configurationType: 'FIELD',
fieldMetadataId: PEOPLE_FIELD_UNIVERSAL_IDENTIFIER,
fieldDisplayMode: 'TABLE',
nestedRelationFieldMetadataId: OPPORTUNITIES_FIELD_UNIVERSAL_IDENTIFIER,
},
}
```
* `fieldMetadataId` přijímá univerzální identifikátor pole na objektu daného rozložení.
* `fieldDisplayMode` je jednou z hodnot `'FIELD'`, `'CARD'`, `'EDITOR'`, `'VIEW'` nebo `'TABLE'`. `TABLE` vkládá zobrazení, které vypisuje záznamy relačního pole typu 1:N.
* `nestedRelationFieldMetadataId` je volitelný a přijímá univerzální identifikátor pole relace typu 1:N na cílovém objektu relace, aby bylo možné vypsat záznamy o dva relační skoky dál (např. stránka Společnosti, která vypisuje příležitosti lidí dané společnosti, nebo stránka Osoby, která vypisuje příležitosti společnosti dané osoby). První skok může být relační pole typu 1:N nebo N:1, druhý musí být 1:N (spojovací relace nejsou podporovány) a vyžaduje `fieldDisplayMode: 'TABLE'` — kombinace s jakýmkoli jiným režimem zobrazení je validační chyba, protože vnořený widget se vždy vykresluje jako vložené zobrazení.
## definePageLayoutTab
Použijte to, když chcete do existujícího rozvržení pouze **přidat** kartu – například kartu analytiky na standardní stránce Company nebo kartu se souhrnem AI připojenou k rozvržení vašeho vlastního objektu.
```ts src/page-layouts/example-extra-tab.ts
import {
definePageLayoutTab,
PageLayoutTabLayoutMode,
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk/define';
import { HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '../front-components/hello-world';
export default definePageLayoutTab({
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000001',
pageLayoutUniversalIdentifier:
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage
.universalIdentifier,
title: 'Hello World',
position: 1000,
icon: 'IconWorld',
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
widgets: [
{
universalIdentifier: 'b1b2b3b4-b5b6-4000-8000-000000000002',
title: 'Hello World',
type: 'FRONT_COMPONENT',
configuration: {
configurationType: 'FRONT_COMPONENT',
frontComponentUniversalIdentifier:
HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
},
},
],
});
```
### Hlavní body
* `pageLayoutUniversalIdentifier` je **povinný** a musí odkazovat na rozvržení stránky, které již existuje v době instalace – buď na standardní rozvržení Twenty, nebo na rozvržení definované vaší vlastní aplikací. Meziaplikační odkazy na rozvržení ve vlastnictví jiné nainstalované aplikace nejsou v současnosti podporovány. Když nadřazené rozvržení chybí, instalace selže s jasnou validační chybou.
* Pro standardní rozvržení Twenty importujte identifikátory z `twenty-sdk/define`:
```ts
import { STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.companyRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.personRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.opportunityRecordPage.universalIdentifier
// STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.noteRecordPage.universalIdentifier
// …
```
Každá položka rozvržení také zpřístupňuje své `tabs` a jejich `widgets`, takže můžete odkazovat na libovolnou úroveň:
```ts
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home
.universalIdentifier;
STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.taskRecordPage.tabs.home.widgets
.fields.universalIdentifier;
```
K dispozici je také krátký alias `STANDARD_PAGE_LAYOUT`:
```ts
import { STANDARD_PAGE_LAYOUT } from 'twenty-sdk/define';
STANDARD_PAGE_LAYOUT.companyRecordPage.universalIdentifier;
```
* `widgets` mají rozsah pouze pro tuto kartu – odkazují na [front components](/l/cs/developers/extend/apps/layout/front-components), zobrazení apod. úplně stejně jako widgety definované přímo v `definePageLayout`.
* `position` určuje pořadí vzhledem ke stávajícím kartám v cílovém rozvržení. Zvolte hodnotu, která umístí vaši kartu tam, kde ji chcete mít, relativně k vestavěným kartám.
* Použijte to místo `definePageLayout`, když chcete do existujícího rozvržení pouze přidat. Použijte `definePageLayout`, když vlastníte celé rozvržení.

View File

@@ -0,0 +1,113 @@
---
title: Zobrazení
description: Dodávejte předem nakonfigurovaná uložená zobrazení – pořadí sloupců, filtry, seskupení – pro objekty ve své aplikaci.
icon: list
---
**Zobrazení** je uložená konfigurace toho, jak se zobrazují záznamy objektu: která pole se zobrazují, v jakém pořadí, zda jsou viditelná a jaké filtry nebo seskupení jsou použity. Pomocí `defineView()` můžete k aplikaci přidat předkonfigurovaná zobrazení.
Každý objekt automaticky získá své hlavní zobrazení seznamu (tabulku „All ...“, kterou otevírá navigační položka `OBJECT`), poskytované serverem – včetně objektů, které vaše aplikace vytváří, s jedním sloupcem pro každé pole, které vaše aplikace deklaruje. Zobrazení, která definujete pomocí `defineView()`, jsou vždy **dodatečná** zobrazení nad rámec tohoto výchozího.
```ts src/views/example-view.ts
import { defineView } from 'twenty-sdk/define';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object';
export default defineView({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'All example items',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
position: 0,
fields: [
{
universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
size: 200,
},
],
});
```
## Hlavní body
* `objectUniversalIdentifier` určuje, na který objekt se toto zobrazení vztahuje. Může to být vlastní objekt, který jste definovali, nebo standardní objekt Twenty.
* Hlavní zobrazení seznamu objektu je vlastněné serverem: `key` je zastaralý a ignorován, takže si je zobrazení definované v manifestu nikdy nemůže nárokovat. Dodejte navigační položku `VIEW`, pokud chcete mít své zobrazení na jedno kliknutí v postranním panelu.
* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`. Chcete-li odkazovat na automaticky vytvořené systémové pole, například `createdAt`, podívejte se na [Cílení na systémová metadata](/l/cs/developers/extend/apps/data/system-fields).
* Pro pokročilé konfigurace můžete také deklarovat `filters`, `filterGroups`, `sorts`, `groups` a `fieldGroups`.
* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení.
## Volitelné vlastnosti
| Vlastnost | Hodnoty | Popis |
| ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | `ViewType.TABLE` (výchozí), `ViewType.LIST`, `ViewType.KANBAN`, `ViewType.CALENDAR` | Jak jsou záznamy uspořádány. (`FIELDS_WIDGET`, `TABLE_WIDGET`, `KANBAN_WIDGET`, `LIST_WIDGET` a `CALENDAR_WIDGET` také existují, ale jsou používány interně widgety rozvržení stránky.) |
| `visibility` | `ViewVisibility.WORKSPACE` (výchozí), `ViewVisibility.UNLISTED` | Zda je zobrazení viditelné v celém pracovním prostoru, nebo není dostupné ve výběrových prvcích. |
| `openRecordIn` | zastaralé | Již se nečte: místo toho je nyní umístění, kde se záznamy otevírají, vlastností [objektu](/l/cs/developers/extend/apps/data/objects) (`openRecordIn` v `defineObject()`), přičemž se v případě potřeby použije individuální předvolba každého člena. |
| `sorts` | `{ fieldMetadataUniversalIdentifier, direction: ViewSortDirection.ASC \| DESC }[]` | Výchozí pořadí řazení. |
| `isCompact` | `boolean` | Kompaktní zobrazení řádků. |
| `mainGroupByFieldMetadataUniversalIdentifier` + `shouldHideEmptyGroups` | — | Seskupení záznamů (např. sloupce kanbanu) podle pole. |
| `kanbanAggregateOperation`, `kanbanAggregateOperationFieldMetadataUniversalIdentifier`, `kanbanColumnWidth` | `AggregateOperations.*` | Agregace a velikost sloupců v kanbanu. |
| `calendarLayout`, `calendarFieldMetadataUniversalIdentifier`, `calendarEndFieldMetadataUniversalIdentifier` | `ViewCalendarLayout.DAY` / `WEEK` / `MONTH` | Zobrazení kalendáře: rozvržení, datumové pole, které určuje umístění záznamů, a volitelné pole koncového data. |
Všechny výše uvedené výčtové typy jsou exportovány z `twenty-sdk/define`.
## Filtry
Zobrazení může být dodáno s předem aplikovanými filtry. Každý filtr má tři souřadnice: **pole**, které se filtruje, **operand** (jak porovnávat) a **hodnotu** (proti čemu porovnávat). Všechny tři musí být v souladu — použití operandu, který se nehodí k typu pole, bude při synchronizaci odmítnuto.
```ts
import { ViewFilterOperand } from 'twenty-sdk/define';
filters: [
{
universalIdentifier: '...',
fieldMetadataUniversalIdentifier: STATUS_FIELD_UNIVERSAL_IDENTIFIER,
operand: ViewFilterOperand.IS,
value: ['ACTIVE'],
},
],
```
### Podporované operandy podle typu pole
| Typ pole | Podporované operandy |
| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `TEXT`, `EMAILS`, `FULL_NAME`, `ADDRESS`, `LINKS`, `PHONES`, `RAW_JSON`, `FILES`, `ACTOR`, `ARRAY` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `ACTOR.source`, `ACTOR.workspaceMemberId` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `SELECT` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `MULTI_SELECT` | `CONTAINS`, `DOES_NOT_CONTAIN`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `RELATION` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `NUMBER` | `IS`, `IS_NOT`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `RATING` | `IS`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `CURRENCY`, `CURRENCY.amountMicros` | `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL`, `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `CURRENCY.currencyCode` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `DATE`, `DATE_TIME` | `IS`, `IS_RELATIVE`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`, `IS_AFTER`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `BOOLEAN` | `IS` |
| `UUID` | `IS`, `IS_NOT`, `IS_EMPTY`, `IS_NOT_EMPTY` |
| `TS_VECTOR` | `VECTOR_SEARCH` |
> Typy polí s podobnými názvy mohou používat zcela odlišné operandy — běžným případem jsou `SELECT` a `MULTI_SELECT`.
### Tvar hodnoty podle operandu
Pole `value` je vždy hodnota serializovatelná do JSON, ale její očekávaný tvar závisí na operandu:
| Skupina operandů | Tvar hodnoty | Příklad |
| --------------------------------------------------------------------- | ----------------------------- | ------------------------ |
| `IS`, `IS_NOT` na `SELECT` | pole klíčů možností (řetězce) | `['ACTIVE', 'PENDING']` |
| `CONTAINS`, `DOES_NOT_CONTAIN` na `MULTI_SELECT` | pole klíčů možností (řetězce) | `['TAG_A']` |
| `IS`, `IS_NOT` na `RELATION` | pole ID záznamů (uuid) | `['c5a1...']` |
| `CONTAINS`, `DOES_NOT_CONTAIN` na textových polích a podobných typech | textový řetězec | `'acme'` |
| `IS`, `IS_NOT` na `NUMBER` | řetězec (hodnota) | `'5'` |
| `IS` na `RATING` / `UUID` | řetězec (hodnota) | `'5'` |
| `GREATER_THAN_OR_EQUAL`, `LESS_THAN_OR_EQUAL` | řetězec (mezní hodnota) | `'10'` |
| `IS`, `IS_BEFORE`, `IS_AFTER` na `DATE` / `DATE_TIME` | řetězec ve formátu ISO 8601 | `'2025-01-01T00:00:00Z'` |
| `IS_EMPTY`, `IS_NOT_EMPTY` | prázdný řetězec | `''` |
| `IS` na `BOOLEAN` | `'true'` nebo `'false'` | `'true'` |
## Jak se zobrazení objevují v uživatelském rozhraní
Samotné zobrazení není z postranního panelu dostupné. Aby se tam zobrazilo, spárujte ho s [položkou navigačního menu](/l/cs/developers/extend/apps/layout/navigation-menu-items) typu `VIEW`, která odkazuje na `universalIdentifier` daného zobrazení. To je kanonický vzor: každý vlastní objekt získá při vytvoření výchozí indexové zobrazení automaticky vygenerované, takže stačí pouze dodat položku v postranním panelu, která jej otevře.

View File

@@ -0,0 +1,197 @@
---
title: Úlohy na pozadí
description: Předejte dlouhotrvající nebo rychlostně omezenou práci Twenty workerům tak, že místo provádění všeho přímo vkládáte do fronty další spuštění logické funkce.
icon: layer-group
---
Běh logické funkce je omezen svým `timeoutSeconds` (maximálně 900 sekund). Cokoli, co se nedokáže dokončit v tomto časovém okně — úplná resynchronizace, fan-out pro jednotlivé záznamy, služba třetí strany, která vás omezuje rychlostí — musí být rozděleno do menších běhů.
`enqueueJobs` dělá přesně to: požádá Twenty workery, aby později spustili jednu z logických funkcí vaší aplikace, jednou pro každý payload, přičemž každé spuštění proběhne ve vlastním procesu s vlastním časovým limitem. Volající se vrátí okamžitě.
```text
┌─────────────────┐ enqueueJobs(...) ┌──────────────┐ ┌────────────────────┐
│ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
│ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
└─────────────────┘ └──────────────┘ └────────────────────┘
```
## Zařazování spuštění do fronty
Importujte `enqueueJobs` z `twenty-sdk/logic-function`, nasměrujte ho na `universalIdentifier` logické funkce, kterou chcete spustit, a předejte do fronty jeden payload pro každé spuštění.
```ts src/logic-functions/sync-all-contacts.ts
import { enqueueJobs } from 'twenty-sdk/logic-function';
await enqueueJobs({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payloads: [{ page: 1 }],
});
```
Každé spuštění přijímá svůj payload jako argument handleru, úplně stejně jako jakýkoli jiný spouštěč. Cíl musí patřit do **stejné aplikace** jako volající – zařazení funkce jiné aplikace do fronty je odmítnuto s chybou `Logic function not found` a nic se nezařadí do fronty. Jedno volání přijímá až `200` payloadů.
<Note>
`enqueueJobs` se vrátí, jakmile jsou úlohy přijaty, nikoli až po jejich spuštění. Nevrací výsledky cílů — nechte každý cíl zapsat, co vytváří, do [úložiště klíč–hodnota](/l/cs/developers/extend/apps/logic/key-value-store) nebo do záznamu v pracovním prostoru, pokud je potřebujete znovu přečíst.
</Note>
<Note>
Starší pomocná funkce `enqueueJob`, která zařazuje do fronty jednu úlohu na volání, je zastaralá. Místo toho použijte `enqueueJobs` se seznamem `payloads` s jedním prvkem.
</Note>
## Možnosti úlohy
Možnosti se vztahují na každé spuštění v dávce.
| Možnost | Výchozí | Rozsah | K čemu slouží |
| ------------ | ------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `retryLimit` | `0` | `0`–`10` | Celkový počet dalších pokusů ve frontě. Počet opakování vyžádaných aplikací je omezen na `3`. Zvyšujte tuto hodnotu jen u handlerů, které je bezpečné spustit dvakrát. |
| `delayMs` | `0` | `0`–`604800000` (7 dní) | Po tuto dobu se čeká, než se spuštění stanou způsobilými. |
```ts
await enqueueJobs({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payloads: [{ page: 1 }],
retryLimit: 3,
delayMs: 60_000,
});
```
<Note>
**Prioritu zatím nelze konfigurovat.** Zařazené úlohy jsou vždy spuštěny s nejnižší prioritou, takže práce platformy není nikdy zdržována úlohami aplikací. Možnost řízení priority brzy přibude.
</Note>
Zařazené spuštění dědí jednajícího uživatele funkce, která ho zařadila, takže pracuje se stejnými oprávněními.
## Opakování při přechodném selhání.
Twenty neopakuje každou výjimku z kódu aplikace. Běžně vyvolaná chyba se považuje za trvalé selhání. Při přechodném selhání může logická funkce zařazená do fronty požádat až o tři opakování vyvoláním `RetryableLogicFunctionError`.
```ts
import {
type LogicFunctionExecutionContext,
RetryableLogicFunctionError,
} from 'twenty-sdk/logic-function';
export const handler = async (
_payload: unknown,
{ retryCount, maxRetries }: LogicFunctionExecutionContext,
) => {
const response = await fetch('https://api.example.com/contacts');
if (response.status === 429 || response.status >= 500) {
throw new RetryableLogicFunctionError(
`The contacts API is temporarily unavailable (${response.status}); retry ${retryCount} of ${maxRetries}`,
);
}
};
```
Pokud je to možné, vyvolejte `RetryableLogicFunctionError` přímo. Pokud ji rozšiřujete, nenahrazujte její `name`: Twenty rozpozná serializovaný název `RetryableLogicFunctionError` napříč běhovými prostředími.
`retryCount` má při počátečním spuštění hodnotu `0` a zvyšuje se pouze tehdy, když kód aplikace požádá o opakování. `maxRetries` je nejvýše `3` a může být nižší, pokud má úloha ve frontě nižší celkový limit opakování. Selhání platformy nezvyšují `retryCount`, přesto však spotřebovávají celkový bezpečnostní rozpočet fronty.
Fronta zpožďuje pokusy o opakování exponenciálním backoffem a jitterem. Přesná prodleva záměrně není zaručena, takže kód aplikace by neměl záviset na tom, že k opakování dojde v přesný čas. Jakmile je dosaženo `maxRetries`, další `RetryableLogicFunctionError` se zaznamená jako konečné selhání aplikace bez dalšího spuštění.
<Warning>
Opakování znovu spustí celou obslužnou funkci a může k nim dojít poté, co některé vedlejší účinky již úspěšně proběhly. Před požádáním o opakování zajistěte, aby byla obslužná funkce idempotentní.
</Warning>
## Použití: stránkování dlouhé synchronizace
Klasický tvar je funkce, která do fronty zařazuje *samu sebe* s dalším kurzorem. Každé spuštění zpracuje jednu stránku práce s velkou rezervou vůči svému vlastnímu časovému limitu a řetězec se zastaví, když už nic nezbývá.
```ts src/logic-functions/sync-contacts-page.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs } from 'twenty-sdk/logic-function';
const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
const handler = async (params: { cursor?: string }) => {
const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
await importContacts(contacts);
if (nextCursor) {
await enqueueJobs({
logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
payloads: [{ cursor: nextCursor }],
delayMs: 2_000,
});
}
return { imported: contacts.length, done: !nextCursor };
};
export default defineLogicFunction({
universalIdentifier: SYNC_CONTACTS_PAGE,
name: 'sync-contacts-page',
timeoutSeconds: 120,
handler,
});
```
## Rozvětvení po záznamech
Když je práce přirozeně po položkách, zařaďte do fronty jednu úlohu na položku v jednom volání a nechte workery zpracovávat je paralelně místo smyčky inline.
```ts
const companies = await listCompaniesToEnrich();
await enqueueJobs({
logicFunctionUniversalIdentifier: ENRICH_COMPANY,
payloads: companies.map((company) => ({ companyId: company.id })),
retryLimit: 2,
});
```
## Doporučené postupy pro dlouhotrvající práci
Dvě pravidla pokryjí téměř každou dlouhou úlohu: **rekurze místo smyčky** a **zpracování omezeného bloku na jedno spuštění**.
Spuštění, které se snaží udělat všechno, je způsob selhání – narazí na časový limit a při opakování začne celé znovu od nuly. Místo toho nastavte velikost jednoho bloku tak, aby se pohodlně vešel do `timeoutSeconds`, uložte si svou pozici a zařaďte do fronty další spuštění.
```ts src/logic-functions/enrich-companies-batch.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs, kv } from 'twenty-sdk/logic-function';
const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
const CHUNK_SIZE = 50;
const handler = async (params: { offset?: number }) => {
const offset = params.offset ?? 0;
const companies = await listCompaniesToEnrich({
offset,
limit: CHUNK_SIZE,
});
for (const company of companies) {
await enrichCompany(company);
}
await kv.set('enrich:progress', { offset: offset + companies.length });
if (companies.length === CHUNK_SIZE) {
await enqueueJobs({
logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
payloads: [{ offset: offset + CHUNK_SIZE }],
});
}
return { processed: companies.length, done: companies.length < CHUNK_SIZE };
};
export default defineLogicFunction({
universalIdentifier: ENRICH_COMPANIES_BATCH,
name: 'enrich-companies-batch',
timeoutSeconds: 300,
handler,
});
```
Co to drží pohromadě:
* **Velikost bloku určete podle nejpomalejší položky, ne podle průměru.** `CHUNK_SIZE × worst-case item time` se musí vejít do `timeoutSeconds` s rezervou, jinak se konec bloku ztratí, když je spuštění utnuto.
* **Udělejte ukončovací podmínku explicitní.** Rekurzi provádějte jen tehdy, když se vrátil plný blok. Řetězec, který se zastavuje jen na základě „žádné výsledky“, poběží donekonečna, pokud zdroj někdy uprostřed vrátí krátkou stránku.
* **Uložte průběh před zařazením dalšího spuštění,** takže se neúspěšný článek řetězu znovu spustí od posledního dokončeného bloku místo od začátku.
* **Udržujte každý blok idempotentní.** Znovuzpracování jednoho bloku po opakování nesmí vést k dvojím zápisům – své zápisy važte na záznam nebo externí ID, které zpracováváte.
* **Preferujte řetězec po blocích před jedním obřím rozvětvením**, když práce zasahuje třetí stranu s omezením rychlosti: řetězec s `delayMs` sám sebe dávkuje, zatímco tisíce úloh zařazených najednou se stanou způsobilými okamžitě.

View File

@@ -0,0 +1,269 @@
---
title: Připojení
description: Umožněte své aplikaci jednat jménem uživatele ve službách třetích stran prostřednictvím OAuth.
icon: plug
---
Připojení jsou pověření, která uživatel uchovává pro externí službu (Linear, GitHub, Slack, ...). Vaše aplikace deklaruje, **jak** se tato pověření získávají — **poskytovatel připojení** — a za běhu je používá k provádění ověřených volání na rozhraní API třetí strany.
V současnosti je podporován pouze OAuth 2.0. Budoucí typy pověření (osobní přístupové tokeny, klíče API, základní autentizace) se připojí ke stejnému rozhraní — aplikace, které již používají `defineConnectionProvider({ type: 'oauth', ... })` nebudou muset migrovat.
<AccordionGroup>
<Accordion title="defineConnectionProvider" description="Definujte, jak vaše aplikace získává připojení">
Poskytovatel připojení popisuje OAuth handshake, který vaše aplikace potřebuje. Uživatel klikne v nastavení vaší aplikace na "Přidat připojení", projde souhlasovou obrazovkou poskytovatele a v jeho pracovním prostoru se vytvoří řádek `ConnectedAccount`.
Funkční nastavení vyžaduje **dva soubory** — poskytovatele připojení a odpovídající deklaraci `serverVariables` v `defineApplication`, která obsahuje klientská pověření OAuth.
```ts src/connection-providers/linear-connection.ts
import { defineConnectionProvider } from 'twenty-sdk/define';
export default defineConnectionProvider({
universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f',
name: 'linear',
displayName: 'Linear',
icon: 'IconBrandLinear',
type: 'oauth',
oauth: {
authorizationEndpoint: 'https://linear.app/oauth/authorize',
tokenEndpoint: 'https://api.linear.app/oauth/token',
scopes: ['read', 'write'],
// These must match keys in `defineApplication.serverVariables` below.
clientIdVariable: 'LINEAR_CLIENT_ID',
clientSecretVariable: 'LINEAR_CLIENT_SECRET',
// Optional: defaults to 'json'. Some providers (Linear, Slack) want
// 'form-urlencoded' for the token request.
tokenRequestContentType: 'form-urlencoded',
// Optional: defaults to true. Disable only if the provider rejects PKCE.
usePkce: false,
// Optional: extra query params on the authorize URL.
// authorizationParams: { prompt: 'consent' },
// Optional: provider's RFC 7009 token revocation endpoint, called on disconnect.
// revokeEndpoint: 'https://example.com/oauth/revoke',
},
// Optional: a logic function in this app to run right after a connection is
// established. See "Run a logic function on connect".
// onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-...-...' },
// Optional: a logic function in this app to run right after a connection is
// removed. See "Run a logic function on disconnect".
// onDisconnectLogicFunction: { universalIdentifier: '4d5e6f70-...-...' },
});
```
```ts src/application.config.ts
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: '...',
displayName: 'Linear',
description: 'Connect Linear to Twenty.',
// OAuth client credentials live on the app registration (one OAuth app per
// Twenty server, configured by the admin) — not per-workspace. Declare them
// as serverVariables so the admin can fill them in once for all installs.
serverVariables: {
LINEAR_CLIENT_ID: {
description: 'OAuth client ID from your Linear OAuth application.',
isSecret: false,
isRequired: true,
},
LINEAR_CLIENT_SECRET: {
description: 'OAuth client secret from your Linear OAuth application.',
isSecret: true,
isRequired: true,
},
},
});
```
Hlavní body:
* `name` je jedinečný identifikátor (řetězec) používaný v `listConnections({ providerName })` (kebab-case, musí odpovídat `^[a-z][a-z0-9-]*$`).
* `displayName` se zobrazuje na kartě nastavení jednotlivé aplikace a v seznamu nástrojů AI.
* `clientIdVariable` / `clientSecretVariable` jsou názvy, ne hodnoty — musí odpovídat klíčům deklarovaným v `defineApplication.serverVariables`. Skutečné `client_id` a `client_secret` zadává správce serveru prostřednictvím rozhraní pro registraci aplikace, nikdy se necommitují do vašeho repozitáře.
* Použijte `serverVariables` (nikoli `applicationVariables`) — pověření OAuth jsou celoserverová a na jeden server Twenty je jedna aplikace OAuth.
* Dokud nejsou vyplněny obě `serverVariables`, karta nastavení aplikace zobrazuje nápovědu "vyžaduje správce serveru" a tlačítko "Přidat připojení" je zakázané.
* `type: 'oauth'` je dnes jediná podporovaná hodnota. Rozlišovač je kompatibilní do budoucna: budoucí typy (`'pat'`, `'api-key'`, ...) přidají nové podbloky konfigurace vedle `oauth`.
URL zpětného volání OAuth, kterou musí váš poskytovatel zařadit na seznam povolených, je:
```
https://<your-twenty-server>/auth/apps/callback
```
</Accordion>
<Accordion title="Spusťte logickou funkci při připojení" description="Reagujte ve chvíli, kdy je připojení navázáno">
Někteří poskytovatelé vám při připojení předají data, která je potřeba uložit dříve, než lze připojení používat — klasickým příkladem je Slack, kde odpověď OAuth určuje `team_id` pracovního prostoru, podle kterého budou příchozí události indexovány. Nastavte `onConnectLogicFunction` tak, aby odkazovala na logickou funkci ve stejné aplikaci (podle jejího `universalIdentifier`), a ta se spustí hned poté, co je vytvořen `ConnectedAccount`.
```ts src/connection-providers/slack-connection.ts
export default defineConnectionProvider({
universalIdentifier: '...',
name: 'slack',
displayName: 'Slack',
type: 'oauth',
oauth: {
/* ... */
},
// Runs claimSlackTeam after every successful Slack connection.
onConnectLogicFunction: {
universalIdentifier: '3a2b1c0d-1111-4222-8333-444455556666',
},
});
```
Hook běží **asynchronně v připojujícím se pracovním prostoru** (je zařazen do fronty, nečeká se na něj), takže pomalý nebo chybující hook nikdy neblokuje ani nenaruší OAuth callback — udělejte jej idempotentní a zajistěte, aby sám zpracovával opakované pokusy. Obslužná funkce přijímá:
```ts
type OnConnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
Odtud použijte `getConnection(connectedAccountId)` ke čtení čerstvého přístupového tokenu a zavolejte API poskytovatele (např. Slack `auth.test`) nebo uložte mapování pomocí [úložiště klíč–hodnota](/l/cs/developers/extend/apps/logic/key-value-store).
</Accordion>
<Accordion title="Spusťte logickou funkci při odpojení" description="Proveďte vyčištění při odstranění připojení">
Cokoli, co aplikace nárokuje v čase připojení, musí být uvolněno, jakmile připojení zanikne. Integrace Slacku, která si například při připojení nárokuje `team_id`, musí tento nárok uvolnit, aby se mohl jiný pracovní prostor připojit ke stejnému týmu ve Slacku. Nastavte `onDisconnectLogicFunction` tak, aby odkazovala na logickou funkci ve stejné aplikaci, a ta se spustí hned poté, co je `ConnectedAccount` smazán.
```ts src/connection-providers/slack-connection.ts
export default defineConnectionProvider({
universalIdentifier: '...',
name: 'slack',
displayName: 'Slack',
type: 'oauth',
oauth: {
/* ... */
},
// Runs releaseSlackTeam after every Slack disconnection.
onDisconnectLogicFunction: {
universalIdentifier: '4470aba8-5ff5-4800-88db-2a427cd8677c',
},
});
```
Stejně jako hook pro připojení běží **asynchronně v odpojovaném pracovním prostoru** a nikdy neblokuje odpojení. Handler dostává stejný tvar payloadu:
```ts
type OnDisconnectPayload = {
connectionProviderId: string;
connectionProviderName: string; // e.g. 'slack'
connectedAccountId: string;
};
```
`ConnectedAccount` už v době spuštění hooku neexistuje, takže `getConnection(connectedAccountId)` už nic nevrátí. Všechno, co čištění potřebuje (například `team_id`, externí ID předplatného), muselo být při připojení zapsáno do [key-value store](/l/cs/developers/extend/apps/logic/key-value-store) pod klíčem `connectedAccountId`.
Hook se spustí, když se připojení odstraní samostatně. Odinstalace aplikace zruší její připojení pomocí kaskádového mazání v databázi, takže se hook v tomto případě nespustí. Pro tento případ deklarujte `uninstallLogicFunction` na `defineApplication`: spustí se před tím, než jsou smazána metadata aplikace, takže může stále volat `listConnections` a dočistit vše, co zůstalo.
</Accordion>
<Accordion title="listConnections / getConnection" description="Použijte připojení z logické funkce">
Uvnitř handleru logické funkce vrací `listConnections({ providerName })` řádky `ConnectedAccount` této aplikace pro daného poskytovatele s obnovenými přístupovými tokeny.
```ts src/logic-functions/handlers/create-linear-issue-handler.ts
import { listConnections } from 'twenty-sdk/logic-function';
export const createLinearIssueHandler = async (input: {
teamId?: string;
title?: string;
}) => {
if (!input.teamId || !input.title) {
return { success: false, error: 'teamId and title are required' };
}
const connections = await listConnections({ providerName: 'linear' });
// Workspace-shared credentials win when present; fall back to the first
// user-visibility one. For HTTP-route triggers you typically pick the
// request user's connection via event.userWorkspaceId instead.
const connection =
connections.find((c) => c.visibility === 'workspace') ?? connections[0];
if (!connection) {
return {
success: false,
error:
'Linear is not connected. Open the app settings and click "Add connection".',
};
}
// Use connection.accessToken to call the third-party API.
const response = await fetch('https://api.linear.app/graphql', {
method: 'POST',
headers: {
Authorization: `Bearer ${connection.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`,
}),
});
return { success: response.ok };
};
```
Každé připojení má:
| Pole | Popis |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Jedinečné ID řádku; předejte do `getConnection(id)` pro opětovné načtení jednoho záznamu |
| `visibility` | `'user'` (soukromé pro jednoho člena pracovního prostoru) nebo `'workspace'` (sdílené se všemi členy) |
| `scopes` | Oprávnění OAuth udělená poskytovatelem (odlišná od `visibility` — ty spolu nesouvisejí) |
| `userWorkspaceId` | ID `userWorkspace` vlastníka — užitečné pro výběr "připojení uživatele požadavku" ve spouštěčích tras HTTP |
| `accessToken` | Aktuální přístupový token OAuth (v případě vypršení je automaticky obnoven) |
| `name` | Zobrazovaný název připojení (automaticky odvozený při OAuth callbacku, uživatelem přejmenovatelný) |
| `handle` | e-mail, který je připojen na předcházející účet, aktualizován při každém opětovném propojení; vyřešeno z OIDC `id_token` vráceno při výměně tokenu, spadne se zpět na spojovací e-mail dvaceti uživatelů, pokud jej poskytovatel nevrátí. |
| `authFailedAt` | Nastaveno, když poslední obnovení selhalo; uživatel se musí znovu připojit |
Hlavní body:
* Předejte `{ providerName }` pro filtrování podle poskytovatele; vynechejte jej, chcete-li získat všechna připojení, která tato aplikace vlastní napříč všemi poskytovateli.
* Server před vrácením výsledku transparentně obnoví přístupový token. Váš handler vždy uvidí použitelný token (nebo nastavené `authFailedAt`).
* `getConnection(id)` je jednořádkový ekvivalent.
</Accordion>
<Accordion title="Viditelnost pro jednotlivce vs. sdílená v pracovním prostoru" description="Jak si uživatelé vybírají mezi soukromými a sdílenými pověřeními">
Když uživatel klikne na "Přidat připojení", je vyzván k výběru viditelnosti:
* **Jen pro mě** — pověření je soukromé pro připojujícího se uživatele. Jakákoli logická funkce volaná jejich jménem (spouštěč HTTP trasy s `isAuthRequired: true`) jej uvidí; spouštěče cron a události databáze nikoli.
* **Sdíleno v pracovním prostoru** — jakýkoli člen pracovního prostoru může pověření použít. Spouštěče cron/databáze jej také uvidí, protože nemají žádného uživatele požadavku.
Pro každý handler použijte tu správnou variantu:
```ts
// HTTP-route trigger — prefer the request user's own connection.
const conn =
connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ??
connections.find((c) => c.visibility === 'workspace');
// Cron trigger — no request user; only shared credentials are sensible.
const conn = connections.find((c) => c.visibility === 'workspace');
```
Více připojení na (uživatele, poskytovatele) je povoleno, takže tentýž uživatel může mít vedle sebe "Personal Linear" a "Work Linear".
</Accordion>
<Accordion title="Jednorázové nastavení poskytovatele" description="Zaregistrujte svou aplikaci OAuth u služby třetí strany">
Pro každého poskytovatele připojení musí správce serveru nejprve zaregistrovat u třetí strany aplikaci OAuth.
1. Přejděte do vývojářského nastavení poskytovatele (např. https://linear.app/settings/api/applications/new).
2. Nastavte **Redirect URI** na `\<SERVER_URL>/auth/apps/callback`.
3. Zkopírujte vygenerované **Client ID** a **Client Secret**.
4. Otevřete nainstalovanou aplikaci v Twenty jako správce serveru → nastavte hodnoty na odpovídajících `serverVariables`.
5. Členové pracovního prostoru pak mohou přidávat připojení v sekci aplikace **Připojení**.
</Accordion>
</AccordionGroup>

View File

@@ -0,0 +1,110 @@
---
title: Úložiště typu klíč–hodnota
description: Ukládejte průběžné výsledky, kešujte data a sdílejte stav mezi spuštěními logických funkcí pomocí vestavěného aplikačního úložiště typu klíč–hodnota.
icon: database
---
Logické funkce běží v izolovaných, krátce žijících procesech Node.js — jakmile běh skončí, nic, co bylo v paměti, nepřežije. Když potřebujete **něco zapamatovat mezi běhy** (kešovat nákladnou odpověď z API, uložit kurzor pro inkrementální synchronizace, odložit práci nebo předat stav z jedné funkce do druhé), uložte to do vestavěného úložiště typu klíč–hodnota.
Každá aplikace má svůj vlastní izolovaný jmenný prostor: položky jsou svázané s ověřenou aplikací, takže vaše klíče nikdy nemohou kolidovat s klíči jiné aplikace ani je jiná aplikace nemůže číst.
```text
┌─────────────────┐ kv.set(key, value) ┌──────────────────────────┐
│ Logic function │ ─────────────────────▶ │ Application KV store │
│ (your handler) │ ◀───────────────────── │ key (unique) │ value │
└─────────────────┘ kv.get(key) └──────────────────────────┘
```
## Get, set, delete
Importujte `kv` z `twenty-sdk/logic-function`. Hodnoty mohou být libovolná JSON-serializovatelná data.
```ts src/logic-functions/sync-linear-issues.ts
import { kv } from 'twenty-sdk/logic-function';
// Read a value. Returns null when the key is missing.
const cursor = await kv.get<string>('sync-cursor:linear');
// Write a value. Creates the entry on first write, updates it afterwards.
await kv.set('sync-cursor:linear', newCursor);
// Delete an entry. Returns true when an entry was removed.
await kv.delete('sync-cursor:linear');
```
## Rozsahy
Každá položka má rozsah, který se předává jako volba při každém volání. Výchozí hodnota je `WORKSPACE`.
* **`WORKSPACE`** (výchozí) — položka je soukromá pro aktuální instalaci vaší aplikace v pracovním prostoru. Každý pracovní prostor, který aplikaci nainstaluje, získá vlastní nezávislou sadu klíčů. To je to, co chcete pro keše, kurzory a stav na úrovni pracovního prostoru.
* **`SERVER`** — položka je sdílena napříč **všemi instalacemi** vaší aplikace na serveru. Serverové položky se chovají jako **nároky (claims)**: uloženou hodnotou je vždy workspaceId, které klíč nárokuje (vynechte `value` při `set`, abyste klíč nárokovali pro aktuální pracovní prostor) a pouze tento pracovní prostor ji může přepsat nebo smazat. Každá instalace může položku číst.
Serverové nároky existují pro směrování napříč pracovními prostory. [Server-route resolver](/l/cs/developers/extend/apps/logic/logic-functions#server-route-trigger) běží v pracovním prostoru vlastníka registrace aplikace, ale příchozí webhook obvykle nese pouze externí id účtu — nikoli Twenty workspaceId. Nechte každý pracovní prostor, aby si při připojení nárokoval své externí id, a poté ho v routě rozřešte:
```ts
// In the connected workspace, when the external account is linked:
await kv.set(`slack:team:${teamId}`, undefined, { scope: 'SERVER' });
// In the server-route resolver (owner workspace), on each webhook:
const workspaceId = await kv.get<string>(`slack:team:${teamId}`, {
scope: 'SERVER',
});
```
Protože serverový klíč může být nárokován pouze pro vlastní pracovní prostor volajícího a nikdy nemůže být přepsán jiným, pracovní prostor nemůže převzít mapování, které patří někomu jinému. `kv.set` vyvolá výjimku, když je klíč už nárokován jiným pracovním prostorem.
## Použití: kešujte nákladné volání
Typickým použitím je kešování pomalé nebo omezované (rate-limited) odpovědi třetí strany, aby opakované běhy znovu použily výsledek místo placení nákladů při každém spuštění.
```ts src/logic-functions/getExchangeRate.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { kv } from 'twenty-sdk/logic-function';
const ONE_HOUR_MS = 60 * 60 * 1000;
type CachedRate = { rate: number; fetchedAt: number };
const handler = async (params: { from: string; to: string }) => {
const cacheKey = `cache:exchange-rate:${params.from}:${params.to}`;
const cached = await kv.get<CachedRate>(cacheKey);
if (cached && Date.now() - cached.fetchedAt < ONE_HOUR_MS) {
return { rate: cached.rate, cached: true };
}
const response = await fetch(
`https://api.example.com/rate?from=${params.from}&to=${params.to}`,
);
const { rate } = (await response.json()) as { rate: number };
await kv.set(cacheKey, { rate, fetchedAt: Date.now() });
return { rate, cached: false };
};
export default defineLogicFunction({
universalIdentifier: 'd9b2f4e6-1c83-4a07-9e52-6b1d3c8a0f47',
name: 'get-exchange-rate',
timeoutSeconds: 10,
handler,
});
```
## Vzorové postupy a tipy
* **Jmenné prostory.** Přidávejte prefixy ke klíčům, abyste oddělili různé oblasti — `sync-cursor:linear`, `cache:exchange-rate:USD:EUR`, `lock:nightly-report`.
* **Expirace (TTL).** Úložiště nemá vestavěnou expiraci. Uložte časové razítko dovnitř hodnoty (jako v příkladu s keší) a při čtení ho kontrolujte, nebo čistěte zastaralé klíče z [funkce spouštěné cronem](/l/cs/developers/extend/apps/logic/logic-functions).
* **Co ukládat.** Jakákoli JSON-serializovatelná hodnota — čísla, řetězce, pole, objekty. Držte záznamy malé; toto je určeno pro koordinaci a kešování, ne pro velké objekty blob nebo soubory. Pro soubory použijte pole `FILES` a [`uploadFile`](/l/cs/developers/extend/apps/logic/logic-functions#uploading-files).
* **Viditelnost.** Položky žijí v databázi instance, ne jako záznamy pracovního prostoru — nikdy se neobjevují v uživatelském rozhraní pracovního prostoru, nejsou součástí datového modelu vaší aplikace a nevyžadují žádná oprávnění k rolím ani objektům.
## Alternativa: dotazovatelný objekt úložiště
Vestavěné úložiště je záměrně neprůhledné: položky nejsou záznamy, takže je nemůžete procházet v UI, propojovat s jinými objekty nebo filtrovat pomocí dotazů na záznamy. Kdykoli něco z toho potřebujete — například viditelný log synchronizace nebo stav na úrovni záznamu — definujte místo toho malý **technický objekt** s jedinečným polem `key` a polem `value` typu `RAW_JSON` a dotazujte ho přes [typovaný API klient](/l/cs/developers/extend/apps/logic/logic-functions#typed-api-clients-twenty-client-sdk). Viz [Objects](/l/cs/developers/extend/apps/data/objects) pro referenci k `defineObject` a [Data → Unique indexes](/l/cs/developers/extend/apps/data/overview#unique-indexes) pro vynucení jedinečnosti klíče.
* **Omezení na záznam.** Přidejte [relaci](/l/cs/developers/extend/apps/data/relations) z objektu úložiště na cílový objekt namísto zakódování id do klíče.
* **Viditelnost a oprávnění.** Řádky žijí v databázi pracovního prostoru jako jakýkoli jiný záznam, takže je lze dotazovat přes API a respektují [role](/l/cs/developers/extend/apps/config/roles) vaší aplikace. Aby se úložiště neobjevovalo v hlavním rozhraní, neuvádějte ho v [navigačním menu](/l/cs/developers/extend/apps/layout/navigation-menu-items).
<Note>
Na rozdíl od vestavěného úložiště je vlastní objekt vždy omezený na jeden pracovní prostor — nemůže sdílet položky napříč instalacemi tak, jako to dělají klíče `SERVER`.
</Note>

View File

@@ -0,0 +1,909 @@
---
title: Logické funkce
description: Definujte serverové funkce v TypeScriptu se spouštěči pro HTTP, cron a databázové události.
icon: bolt
---
Logické funkce jsou serverové funkce v TypeScriptu, které běží na platformě Twenty. Mohou být spouštěny požadavky HTTP, plány cronu nebo databázovými událostmi — a lze je také zpřístupnit jako nástroje pro agenty AI.
<AccordionGroup>
<Accordion title="defineLogicFunction" description="Definujte logické funkce a jejich spouštěče">
Každý soubor funkce používá `defineLogicFunction()` k exportu konfigurace s obslužnou funkcí (handlerem) a volitelnými spouštěči.
```ts src/logic-functions/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (params: RoutePayload) => {
const client = new CoreApiClient();
const body = (params.body ?? {}) as { name?: string };
const name = body.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world';
const result = await client.mutation({
createPostCard: {
__args: { data: { name } },
id: true,
name: true,
},
});
return result;
};
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'create-new-post-card',
timeoutSeconds: 2,
handler,
httpRouteTriggerSettings: {
path: '/post-card/create',
httpMethod: 'POST',
isAuthRequired: true,
},
/*databaseEventTriggerSettings: {
eventName: 'people.created',
},*/
/*cronTriggerSettings: {
pattern: '0 0 1 1 *',
},*/
});
```
Dostupné typy spouštěčů:
* **httpRoute**: Zpřístupní vaši funkci na HTTP cestě a metodě. V kódu aplikace přidejte prefix `/s/` k cestě routy při použití `RestApiClient`; nasazená URL používá injektovanou základní adresu `TWENTY_FUNCTIONS_URL` (nebo `\<server-url>/s`, pokud není nastavena).
<Note>
Chcete-li vyvolat logickou funkci spuštěnou trasou z (bezhlavé) front-endové komponenty, podívejte se na [Volání logické funkce](/l/cs/developers/extend/apps/layout/front-components#calling-a-logic-function).
</Note>
* **cron**: Spouští vaši funkci podle plánu pomocí výrazu CRON.
* **databaseEvent**: Spouští se při událostech životního cyklu objektů v pracovním prostoru. Když je operace události `updated`, lze konkrétní sledovaná pole určit v poli `updatedFields`. Pokud zůstane nedefinované nebo prázdné, spustí funkci jakákoli aktualizace.
> např. `person.updated`, `*.created`, `company.*`
* **serverRoute**: Zpřístupňuje jednu registrací omezenou trasu HTTP. Funkce **resolver** (deklarovaná pomocí `serverRouteTriggerSettings`) běží ve vlastnickém workspace a buď vrátí synchronní `Response`, nebo cílový workspace a logickou funkci, která se má zařadit do fronty; v případě zařazení platforma potvrdí přijetí kódem `202` a spustí tuto **cílovou** funkci ve frontě workeru. Viz [spouštěč serverové trasy](#server-route-trigger).
<Note>
Funkci můžete také spustit ručně pomocí CLI:
```bash filename="Terminal"
yarn twenty dev:function:exec -n create-new-post-card -p '{"key": "value"}'
```
```bash filename="Terminal"
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
Logy můžete sledovat pomocí:
```bash filename="Terminal"
yarn twenty dev:function:logs
```
</Note>
#### Payload spouštěče trasy
Když spouštěč typu route vyvolá vaši logickou funkci, ta obdrží objekt `RoutePayload`, který odpovídá
[AWS HTTP API v2 formátu](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html).
Importujte typ `RoutePayload` z `twenty-sdk/logic-function`:
```ts
import type { RoutePayload } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
const { headers, queryStringParameters, pathParameters, body } = event;
const { method, path } = event.requestContext.http;
return { message: 'Success' };
};
```
Typ `RoutePayload` má následující strukturu:
| Vlastnost | Typ | Popis | Příklad |
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `headers` | `Record\<string, string \| undefined>` | Záhlaví HTTP (pouze ta uvedená v `forwardedRequestHeaders`) | viz sekci níže |
| `queryStringParameters` | `Record\<string, string \| undefined>` | Parametry query stringu (více hodnot spojených čárkami) | `/users?ids=1&ids=2&ids=3&name=Alice` -> `{ ids: '1,2,3', name: 'Alice' }` |
| `pathParameters` | `Record\<string, string \| undefined>` | Parametry cesty extrahované ze vzoru trasy | `/users/:id`, `/users/123` -> `{ id: '123' }` |
| `body` | `object \| null` | Parsované tělo požadavku (JSON) | `{ id: 1 }` -> `{ id: 1 }` |
| `rawBody` | `string \| undefined` | Původní tělo požadavku v UTF-8, před parsováním JSONu. Užitečné pro ověřování podpisů webhooků typu HMAC (např. GitHubův `X-Hub-Signature-256`, Stripe). `undefined`, pokud jej běhové prostředí nezachovalo. | |
| `isBase64Encoded` | `boolean` | Zda je tělo kódováno base64 | |
| `requestContext.http.method` | `string` | Metoda HTTP (GET, POST, PUT, PATCH, DELETE) | |
| `requestContext.http.path` | `string` | Nezpracovaná cesta požadavku | |
#### forwardedRequestHeaders
Ve výchozím nastavení se záhlaví HTTP z příchozích požadavků z bezpečnostních důvodů do vaší logické funkce **ne** předávají.
Chcete-li zpřístupnit konkrétní záhlaví, výslovně je uveďte v poli `forwardedRequestHeaders`:
```ts
export default defineLogicFunction({
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'webhook-handler',
handler,
httpRouteTriggerSettings: {
path: '/webhook',
httpMethod: 'POST',
isAuthRequired: false,
forwardedRequestHeaders: ['x-webhook-signature', 'content-type'],
},
});
```
Ve vašem handleru k přeposlaným záhlavím přistupujte takto:
```ts
const handler = async (event: RoutePayload) => {
const signature = event.headers['x-webhook-signature'];
const contentType = event.headers['content-type'];
// Validate webhook signature...
return { received: true };
};
```
<Note>
Názvy záhlaví jsou normalizovány na malá písmena. Přistupujte k nim pomocí klíčů s malými písmeny (například `event.headers['content-type']`).
</Note>
#### Vlastní odpověď HTTP
Ve výchozím nastavení vrácení prosté hodnoty z vašeho handleru odešle tuto hodnotu zpět jako odpověď `200` (JSON pro objekty, `text/plain` pro řetězce). Pro kontrolu stavového kódu a hlaviček odpovědi vraťte `Response` z `twenty-sdk/logic-function`:
```ts
import { Response } from 'twenty-sdk/logic-function';
const handler = async (event: RoutePayload) => {
return new Response('<h1>Hello</h1>', {
status: 201,
headers: { 'content-type': 'text/html' },
});
};
```
Z bezpečnostních důvodů jsou hlavičky odpovědi omezeny na seznam povolených položek. Jakákoli hlavička, která není na seznamu (např. `Set-Cookie`, CORS hlavičky jako `Access-Control-Allow-Origin` nebo vlastní hlavičky `X-*`), je tiše zahozena před odesláním odpovědi. Povolené hlavičky odpovědi jsou:
* `content-type`
* `content-language`
* `content-disposition`
* `cache-control`
* `retry-after`
<Note>
Stavový kód musí být platný stavový kód HTTP (mezi 100 a 599). Názvy hlaviček odpovědi se porovnávají bez rozlišení velikosti písmen.
</Note>
#### Chybové odpovědi platformy
Kromě vlastních odpovědí vašeho handleru platforma v některých situacích odpovídá na volání rout přímo: `404`, když route nebo funkce neexistuje, `403`, když je aplikace zastavena, `429`, když je dosaženo limitu rychlosti vykonávání, a `422`, když jsou produkční `dependencies` aplikace příliš velké na to, aby se nainstalovaly — viz [dependencies size limits](/l/cs/developers/extend/apps/getting-started/project-structure#dependencies).
#### Spouštěč serverové trasy
`httpRouteTriggerSettings` zpřístupňuje funkci pod `/s/` a workspace určuje z hostitele požadavku — což funguje, když má každý workspace svou vlastní doménu. Poskytovatelé třetích stran však doručují události každého tenanta na **jednu** adresu URL. Pro tento případ použijte `serverRouteTriggerSettings`.
Spouštěč má dvě části:
1. Logická funkce **resolveru** — deklarovaná pomocí `serverRouteTriggerSettings` — běží ve vašem **vlastnickém workspace** (workspace, který je vlastníkem registrace aplikace). Prozkoumá příchozí požadavek a vrátí buď:
* `{ workspaceId, targetLogicFunctionUniversalIdentifier, payload? }` — platforma zařadí tento cíl do fronty v určeném workspace a potvrdí přijetí s `202 { queued: true }`, nebo
* `Response` z `twenty-sdk/logic-function` — platforma tento HTTP response vrátí **synchronně** a cíl do fronty **ne**zařadí (použijte pro ověřovací handshake, například Slack `url_verification`).
Resolver je jediným místem autorizace — URL nese pouze identifikátor resolveru. **Toto je preferované místo pro ověřování podpisů requestů**: resolver běží před jakýmikoli vedlejšími efekty, má přístup k původnímu `rawBody` a předaným hlavičkám a může request odmítnout, aniž by se vůbec dotkl cíle.
2. Cílová (**target**) logická funkce — běžná per-workspace logická funkce — pak běží v určeném workspace s payloadem vráceným resolverem (nebo s původním payloadem requestu, pokud jej resolver neupravil). Návratovou hodnotu volající HTTP **nevidí**, když resolver zvolí cestu zařazení do fronty.
```ts src/logic-functions/resolve-server-route.logic-function.ts
import { createHmac, timingSafeEqual } from 'crypto';
import { defineLogicFunction } from 'twenty-sdk/define';
import { Response, type RoutePayload } from 'twenty-sdk/logic-function';
// Runs in the owner workspace. Verifies the request signature, picks
// which target function should handle the event, and returns the
// workspace + target the platform should dispatch to.
const handler = async (event: RoutePayload) => {
// Fail closed if the secret isn't configured — never fall back to an
// empty key, which would let any caller forge a matching signature.
const secret = process.env.GITHUB_WEBHOOK_SECRET;
if (!secret) {
throw new Error('GITHUB_WEBHOOK_SECRET is not configured');
}
const signature = event.headers['x-hub-signature-256'] ?? '';
const expected =
'sha256=' +
createHmac('sha256', secret).update(event.rawBody ?? '').digest('hex');
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !timingSafeEqual(a, b)) {
throw new Error('invalid signature');
}
const body = (event.body ?? {}) as {
challenge?: string;
metadata?: { twentyWorkspaceId?: string };
type?: string;
};
// Handshakes must be answered on this same response, so reply from the
// resolver instead of returning a dispatch target.
if (body.type === 'url_verification') {
return new Response({ challenge: body.challenge });
}
const workspaceId = body.metadata?.twentyWorkspaceId;
if (!workspaceId) {
throw new Error('event is not linked to a workspace');
}
return {
workspaceId,
// Route different event types to different target functions.
targetLogicFunctionUniversalIdentifier:
body.type === 'invoice.paid'
? 'c4e2a9b1-7d4e-4c9a-9f2b-2e1d6a4c8e10' // handle-invoice-paid
: 'd5f3b0c2-8e5f-5d0b-a0c3-3f2e7b5d9f21', // handle-other-event
};
};
export default defineLogicFunction({
universalIdentifier: 'b3c2f0a1-7d4e-4c9a-9f2b-2e1d6a4c8e10',
name: 'resolve-server-route',
handler,
serverRouteTriggerSettings: {
forwardedRequestHeaders: ['x-hub-signature-256'],
},
});
```
```ts src/logic-functions/handle-invoice-paid.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import type { RoutePayload } from 'twenty-sdk/logic-function';
// Runs in the resolved workspace. The resolver has already authenticated
// the request, so this handler can focus on the actual work.
const handler = async (event: RoutePayload) => {
// ...handle the verified event
return { received: true };
};
export default defineLogicFunction({
universalIdentifier: 'c4e2a9b1-7d4e-4c9a-9f2b-2e1d6a4c8e10',
name: 'handle-invoice-paid',
handler,
});
```
Endpoint je dostupný na:
```
POST https://your-twenty-server.com/webhooks/server/:resolverLogicFunctionUniversalIdentifier
```
Identifikátor je `universalIdentifier` resolveru z vašeho manifestu. Zaregistrujte tuto adresu URL u poskytovatele.
**Odpověď na ověřovací požadavek GET.** Někteří poskytovatelé ověřují koncový bod před doručováním tím, že na stejnou adresu URL, na kterou budou později pomocí `POST` odesílat události, zašlou požadavek `GET` s výzvou — jedním z nich je WhatsApp Cloud API od společnosti Meta. Trasa serveru odpovídá pouze na `POST`, pokud neuvedete jinak, proto deklarujte obě metody:
```ts
serverRouteTriggerSettings: {
forwardedRequestHeaders: ['x-hub-signature-256'],
httpMethods: ['GET', 'POST'],
}
```
Výzva dorazí v `event.queryStringParameters` a vrácením objektu `Response` ji ve stejném požadavku odešlete zpět poskytovateli. Tělo řetězce se odesílá jako `text/plain`, což tito poskytovatelé očekávají:
```ts
const handler = async (event: RoutePayload) => {
if (event.requestContext.http.method === 'GET') {
return new Response(event.queryStringParameters['hub.challenge']);
}
// …handle the delivered event
};
```
`httpMethods` nahrazuje výchozí nastavení, namísto aby je doplňovalo, takže samotné `['GET']` způsobí, že trasa odmítne `POST`. Podporovány jsou pouze `GET` a `POST`. Ponechte je nenastavené, pokud poskytovatel nepotřebuje druhé sloveso: trasa, která deklaruje `GET`, bude mít svůj resolver spuštěný jakýmkoli neověřeným volajícím, včetně crawlerů a nástrojů pro rozbalení odkazů, které bez vyžádání odesílají `GET`. Na cokoli, pro co platforma nemá metodu, se odpoví `405`, aniž by se resolver kdy spustil.
<Note>
**Aplikace musí být převzata do vlastnictví a nainstalována v pracovním prostoru vlastníka.** Protože resolver běží v **pracovním prostoru vlastníka** (pracovní prostor, který vlastní registraci aplikace), spouštěč serverové trasy funguje pouze tehdy, když byla aplikace *převzata do vlastnictví* — tj. má pracovní prostor vlastníka — **a** tato aplikace je **nainstalována v pracovním prostoru vlastníka**. Dokud nejsou obě podmínky splněny, resolver nemá kde běžet, takže trasu nelze zpracovat. Aplikace, která zpřístupňuje logickou funkci `serverRouteTriggerSettings`, proto nemůže být uvedena na Marketplace, dokud není převzata do vlastnictví a nainstalována v pracovním prostoru vlastníka.
</Note>
**Smlouva resolveru.** Typ `LogicFunctionConfig` v SDK toto vynucuje v době kompilace: jakmile nastavíte `serverRouteTriggerSettings`, váš handler je omezen tak, aby vracel buď `Response`, nebo `{ workspaceId: string; targetLogicFunctionUniversalIdentifier: string; payload?: object }` (nebo `Promise` jedné z těchto možností). Na dispatch cestě musí být `workspaceId` workspace, ve kterém je cílová funkce nainstalována, jinak je request odmítnut s chybou `404`. Výsledek, který neodpovídá ani jedné z těchto struktur — včetně takového, jehož identifikátory nejsou UUID — je odmítnut s chybou `502`.
| Pole | Typ | Poznámky |
| ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------ |
| `workspaceId` | `string` | Workspace UUID, ve kterém bude cíl spuštěn. |
| `targetLogicFunctionUniversalIdentifier` | `string` | `universalIdentifier` logické funkce, která má být v daném workspace spuštěna. |
| `payload` | `object` (nepovinné) | Pokud je nastaven, nahradí tělo requestu odeslané na cíl. |
<Warning>
**Ověření podpisu je vaší odpovědností — proveďte ho v resolveru.** Platforma nepřezkušuje (neověřuje) podpisy requestů. Resolver je k tomu doporučené místo: běží jako první, má přístup k `event.rawBody` a hlavičkám, které jste uvedli v `forwardedRequestHeaders`, a vyhozená chyba (nebo jakékoli neodpovídající `workspaceId`) zastaví předání dříve, než je cíl zavolán. Pokud místo toho posunete ověřování až do cíle, cíl musí dávat pozor, aby neztratil `rawBody` a hlavičky — tj. resolver nesmí vracet `payload`. Vždy ověřujte **před** jakýmikoliv vedlejšími efekty a použijte porovnání v konstantním čase.
</Warning>
U podpisů requestů většina poskytovatelů podepisuje pomocí HMAC-SHA256; části, které se liší, jsou název hlavičky, kódování digestu a podepsaný řetězec payloadu. Několik příkladů:
| Poskytovatel | Hlavičky k přeposlání | Podepsaný řetězec | Digest |
| ---------------------------- | ------------------------------------------------------ | ---------------------------- | ----------------------------------------------------- |
| Svix (Recall, Resend, Clerk) | `webhook-id`, `webhook-timestamp`, `webhook-signature` | `{id}.{timestamp}.{rawBody}` | base64 (tajemství je v base64 po odstranění `whsec_`) |
| Stripe | `stripe-signature` | `{timestamp}.{rawBody}` | hex |
| GitHub | `x-hub-signature-256` | `{rawBody}` | hex (s prefixem `sha256=`) |
| Shopify | `x-shopify-hmac-sha256` | `{rawBody}` | base64 |
| Slack | `x-slack-signature`, `x-slack-request-timestamp` | `v0:{timestamp}:{rawBody}` | hex (s prefixem `v0=`) |
Příklad resolveru výše už ukazuje GitHub HMAC-SHA256 flow — přizpůsobte název hlavičky, kódování digestu a podepsaný řetězec payloadu podle poskytovatele, se kterým se integrujete.
<Note>
Když resolver vrátí dispatch objekt, route odpoví `202 { queued: true }` a cíl běží ve frontě workeru — volající nikdy nevidí latenci cíle, výsledek ani chyby (ty jsou zaznamenané v logách běhu). Tím se zabrání tomu, aby opakované doručování na straně odesílatele znásobovalo zpomalení zpracování, což je přesně to, co chcete pro příjem webhooků.
Když volající musí v rámci stejného requestu přečíst tělo odpovědi (challenge handshaky, interaktivní potvrzení), vraťte místo toho z **resolveru** `Response`. Platforma jej synchronně zopakuje a přeskočí frontu; jeho hlavičky procházejí stejným seznamem povolených položek jako odpovědi HTTP rout. Udržujte resolver rychlý — některým poskytovatelům (např. Slack) vyprší časový limit během několika sekund. Protože je resolver dostupný jako veřejný endpoint, chraňte ho omezením rychlosti (rate limiting) na své edge vrstvě.
</Note>
#### Payload spouštěče databázové události
Když spouštěč databázové události vyvolá vaši logickou funkci, obdrží jeden `DatabaseEventPayload` pro každý změněný záznam. Payload kombinuje metadata o zdrojovém pracovním prostoru a objektu s událostí na úrovni záznamu.
```ts
import type {
DatabaseEventPayload,
ObjectRecordCreateEvent,
ObjectRecordDestroyEvent,
ObjectRecordUpdateEvent,
} from 'twenty-sdk/logic-function';
type Person = {
id: string;
emails?: { primaryEmail?: string };
};
```
Tělo zprávy obsahuje:
| Vlastnost | Popis |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `name` | Název události, například `person.updated`. |
| `workspaceId` | Pracovní prostor, ve kterém k události došlo. |
| `objectMetadata` | Metadata objektu, který se změnil. |
| `recordId` | ID změněného záznamu. |
| `userId`, `userWorkspaceId`, `workspaceMemberId` | Pole aktéra, pokud byla událost způsobena uživatelem pracovního prostoru. |
| `properties` | Data záznamu pro událost, s `before`, `after`, `diff` a `updatedFields` v závislosti na operaci. |
| Událost | Data záznamu |
| ------------------ | -------------------------------------------------------------------------------------------------------------- |
| `person.created` | `event.properties.after` |
| `person.updated` | `event.properties.before`, `event.properties.after`, `event.properties.diff`, `event.properties.updatedFields` |
| `person.destroyed` | `event.properties.before` |
U logických smazání má `.deleted` podobu jako u aktualizace, protože se změní pole `deletedAt` záznamu.
Pro trvalá smazání použijte `.destroyed`.
<Note>
`databaseEventTriggerSettings.updatedFields` filtruje, které události aktualizace spustí funkci.
`event.properties.updatedFields` říká, která pole se v aktuální události skutečně změnila.
</Note>
Příklad události vytvoření:
```ts
type PersonCreatedEvent = DatabaseEventPayload<
ObjectRecordCreateEvent<Person>
>;
const handler = async (event: PersonCreatedEvent) => {
const person = event.properties.after;
return {
personId: event.recordId,
email: person.emails?.primaryEmail,
};
};
```
Příklad události aktualizace:
```ts
type PersonUpdatedEvent = DatabaseEventPayload<
ObjectRecordUpdateEvent<Person>
>;
const handler = async (event: PersonUpdatedEvent) => {
const { before, after, diff, updatedFields } = event.properties;
return {
personId: event.recordId,
updatedFields,
previousEmail: before.emails?.primaryEmail,
currentEmail: after.emails?.primaryEmail,
emailDiff: diff.emails,
};
};
```
Spouštění pouze při aktualizacích e‑mailu:
```ts
export default defineLogicFunction({
...,
databaseEventTriggerSettings: {
eventName: 'person.updated',
updatedFields: ['emails'],
},
});
```
Příklad události smazání:
```ts
type PersonDestroyedEvent = DatabaseEventPayload<
ObjectRecordDestroyEvent<Person>
>;
const handler = async (event: PersonDestroyedEvent) => {
const personBeforeDestroy = event.properties.before;
return {
personId: event.recordId,
email: personBeforeDestroy.emails?.primaryEmail,
};
};
```
#### Kontext provádění
Každá obslužná funkce obdrží druhý argument popisující samotný běh bez ohledu na to, co jej spustilo. Zatímco tvar prvního argumentu se liší podle spouštěče, tento se nemění:
```ts
import type { LogicFunctionExecutionContext } from 'twenty-sdk/logic-function';
const handler = async (
event: DatabaseEventPayload,
{ workspaceId, workspaceMemberId }: LogicFunctionExecutionContext,
) => {
if (workspaceMemberId === null) {
throw new Error('This function must be triggered by a person');
}
return { reviewedBy: workspaceMemberId, workspaceId };
};
```
| Pole | Typ | Popis |
| ------------------- | ---------------- | -------------------------------------------------------------------------------------------- |
| `workspaceId` | `string` | Pracovní prostor, ke kterému běh patří. |
| `userWorkspaceId` | `string \| null` | Osoba, která běh spustila, jako její členství v tomto pracovním prostoru. |
| `workspaceMemberId` | `string \| null` | Tatáž osoba jako záznam člena pracovního prostoru, což je to, k čemu propojujete záznamy. |
| `retryCount` | `number` | `0` při prvním pokusu. Viz [Úlohy na pozadí](/l/cs/developers/extend/apps/logic/background-jobs). |
| `maxRetries` | `number` | Horní limit opakovaných pokusů požadovaných aplikací. |
`userWorkspaceId` a `workspaceMemberId` mají hodnotu `null`, když běh nikdo nespustil: plány cron, hooky instalace a neověřené webhooky za sebou nemají žádnou osobu. Hodnotu `null` mají také tehdy, když osoba nemá záznam člena pracovního prostoru nebo když byl její záznam odstraněn.
<Note>
**Kontext vám sdělí, kdo běh spustil.** To, co běh smí *dělat*, je samostatná deklarace uvedená níže.
</Note>
#### Čí přístup volání používá
Každý klient — `CoreApiClient`, `MetadataApiClient` a `RestApiClient` — jedná jako osoba, která běh spustila: její role se protíná s rolí vaší aplikace, takže volání nikdy nemůže udělat více, než může kterýkoli z vás. Toto je výchozí chování a znamená, že osoba nikdy nemůže použít vaši aplikaci k překročení vlastních oprávnění.
Když běh nikdo nespustil, není žádná osoba, za kterou by bylo možné jednat, takže stejný klient přejde na vlastní přístup vaší aplikace: plány cron, hooky instalace a neověřené webhooky nevyžadují zvláštní zpracování.
Některá volání oprávněně potřebují vlastní přístup aplikace, i když za během *je* osoba — například pro čtení konfiguračních záznamů vaší aplikace nebo provádění práce, kterou by tato osoba sama nemohla vykonat. Pro tyto případy vytvořte druhého klienta:
```ts
import { CoreApiClient } from 'twenty-client-sdk/core';
const asPerson = new CoreApiClient();
const asApplication = new CoreApiClient({ runAs: 'application' });
const handler = async (event: DatabaseEventPayload) => {
// config records only your app may read
const { slackUserLinks } = await asApplication.query({ ... });
// created as the person, so it is attributed and permission-checked
await asPerson.mutation({ createNote: { ... } });
};
```
Oba vytvořte jednou v rozsahu modulu a každé místo volání pak podle volaného klienta určí, který přístup používá. `RestApiClient` přijímá stejnou možnost:
```ts
import { RestApiClient } from 'twenty-client-sdk/rest';
const asApplication = new RestApiClient({ runAs: 'application' });
```
<Note>
**Běh, který nikdo nespustil, jedná jako vaše aplikace.** Plány cron, hooky instalace a neověřené webhooky za sebou nemají žádnou osobu, takže výchozí klient přejde na vlastní přístup vaší aplikace a nadále funguje. `runAs: 'application'` je potřeba jen tehdy, když tento přístup chcete při běhu, který osoba *skutečně* spustila.
Když se funkce chová odlišně podle toho, zda za ní někdo stojí, zkontrolujte `context.workspaceMemberId`, například pro přiřazení záznamu.
</Note>
Pomocné funkce SDK, které přistupují k vlastním prostředkům vaší aplikace, vždy používají její přístup a ignorují `runAs`: [úložiště klíč-hodnota](/l/cs/developers/extend/apps/logic/key-value-store), [připojení](/l/cs/developers/extend/apps/logic/connections), `runAgent`, `getPublicAssetUrl` a účtování kreditů.
#### Zpřístupnění funkce jako nástroje AI nebo akce pracovního postupu
Logické funkce lze zpřístupnit na dvou rozhraních, z nichž každé má vlastní spouštěč:
* **`toolTriggerSettings`** — zpřístupní funkci AI funkcím Twenty (chat, MCP, volání funkcí). Používá standardní JSON Schema, formát, kterému modely LLM nativně rozumějí.
* **`workflowActionTriggerSettings`** — zobrazí funkci jako krok ve vizuálním builderu workflow. Používá bohaté `InputSchema` od Twenty, aby builder mohl vykreslit správné editory polí, voliče proměnných a štítky.
Funkce se může rozhodnout pro jedno, druhé nebo obě. Stojí po boku `cronTriggerSettings`, `databaseEventTriggerSettings` a `httpRouteTriggerSettings` — stejný vzor, stejná struktura.
<Note>
**Vztah k akci Code ve workflow.** Vestavěná akce **Code** v tvůrci workflow je sama o sobě logická funkce — Twenty pro každý krok Code vytvoří jednu a její editor zpřístupní inline. `workflowActionTriggerSettings` je způsob, jak z jednorázového inline kódu udělat **znovupoužitelnou** akci: funkci v aplikaci nadefinujete jednou a potom je možné ji vybrat v libovolném workflow, místo aby se kopírovala do každého kroku Code. Pro pohled koncového uživatele se podívejte na [akci Code](/l/cs/user-guide/workflows/capabilities/workflow-actions#code) v uživatelské příručce.
</Note>
```ts src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { CoreApiClient } from 'twenty-client-sdk/core';
const handler = async (params: { companyName: string; domain?: string }) => {
const client = new CoreApiClient();
const result = await client.mutation({
createTask: {
__args: {
data: {
title: `Enrich data for ${params.companyName}`,
body: `Domain: ${params.domain ?? 'unknown'}`,
},
},
id: true,
},
});
return { taskId: result.createTask.id };
};
export default defineLogicFunction({
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
name: 'enrich-company',
description: 'Enrich a company record with external data',
timeoutSeconds: 10,
handler,
toolTriggerSettings: {},
});
```
Hlavní body:
* Funkce může míchat rozhraní — deklarujte jak `toolTriggerSettings`, tak `workflowActionTriggerSettings`, abyste ji zpřístupnili v chatu i ve workflow builderu.
* `toolTriggerSettings.inputSchema` a `workflowActionTriggerSettings.inputSchema` jsou obě volitelné. Pokud jsou vynechány, sestavovač manifestu je odvodí ze zdrojového kódu handleru (JSON Schema pro nástroj AI, `InputSchema` od Twenty pro akci workflow). Uveďte jej explicitně, když chcete bohatší typování — například u polí s podporou `FieldMetadataType`, jako `CURRENCY` nebo `RELATION` pro workflow builder, nebo s poli `description`, která si AI agent může přečíst:
```ts
export default defineLogicFunction({
...,
toolTriggerSettings: {
inputSchema: {
type: 'object',
properties: {
companyName: {
type: 'string',
description: 'The name of the company to enrich',
},
domain: {
type: 'string',
description: 'The company website domain (optional)',
},
},
required: ['companyName'],
},
},
});
```
Abyste deklarovali své parametry **jen jednou** a obsloužili obě rozhraní, definujte jedno JSON Schema (`InputJsonSchema`) a převeďte jej pro akci pracovního postupu pomocí `jsonSchemaToInputSchema` z `twenty-sdk/logic-function`. `toolTriggerSettings.inputSchema` přebírá JSON Schema přímo, zatímco `workflowActionTriggerSettings.inputSchema` očekává `InputSchema` od Twenty:
```ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema, type InputJsonSchema } from 'twenty-sdk/logic-function';
const inputSchema: InputJsonSchema = {
type: 'object',
properties: {
companyName: { type: 'string', label: 'Company name' },
domain: { type: 'string', label: 'Domain' },
},
required: ['companyName'],
};
export default defineLogicFunction({
...,
toolTriggerSettings: { inputSchema },
workflowActionTriggerSettings: {
label: 'Enrich Company',
icon: 'IconBuilding',
inputSchema: jsonSchemaToInputSchema(inputSchema),
},
});
```
##### Kompletní příklad akce workflow
`workflowActionTriggerSettings` přijímá čtyři pole:
| Pole | Účel |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label` | Název zobrazený pro akci ve výběru kroků tvůrce workflow. Výchozí hodnota je `name` funkce. |
| `icon` | Ikona zobrazená vedle akce (název z `tabler-icons`, např. `IconBuilding`). |
| `inputSchema` | Rozšířený `InputSchema` Twenty — to, co tvůrce zobrazuje jako konfigurovatelná pole (s výběrem proměnných). Volitelné; pokud je vynecháno, odvodí se z handleru. |
| `outputSchema` | Definuje strukturu, kterou handler vrací, aby na **jeho výstupní pole mohly navazující kroky mapovat**. Volitelné; bez něj je výstup zpřístupněn jako jedna neprůhledná hodnota. |
Dohromady — funkce zpřístupněná jako akce workflow s deklarovaným výstupem, aby se na `taskId` mohly odkazovat pozdější kroky:
```ts src/logic-functions/enrich-company.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema, type InputJsonSchema } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';
const inputSchema: InputJsonSchema = {
type: 'object',
properties: {
companyName: { type: 'string', label: 'Company name' },
domain: { type: 'string', label: 'Domain' },
},
required: ['companyName'],
};
const handler = async (params: { companyName: string; domain?: string }) => {
const client = new CoreApiClient();
const result = await client.mutation({
createTask: {
__args: {
data: {
title: `Enrich data for ${params.companyName}`,
body: `Domain: ${params.domain ?? 'unknown'}`,
},
},
id: true,
},
});
// The keys returned here should match the `outputSchema` properties below.
return { taskId: result.createTask.id, enriched: true };
};
export default defineLogicFunction({
universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
name: 'enrich-company',
description: 'Enrich a company record with external data',
timeoutSeconds: 10,
handler,
workflowActionTriggerSettings: {
label: 'Enrich Company',
icon: 'IconBuilding',
inputSchema: jsonSchemaToInputSchema(inputSchema),
outputSchema: [
{
type: 'object',
properties: {
taskId: { type: 'string' },
enriched: { type: 'boolean' },
},
},
],
},
});
```
Jakmile je aplikace nainstalovaná, **Enrich Company** se zobrazí ve výběru akcí tvůrce workflow. Tvůrce zobrazí `companyName` a `domain` jako vstupní pole (každé z nich může získávat hodnoty z předchozích kroků) a následující kroky se mohou odkazovat na výstupy kroku `taskId` a `enriched`.
<Note>
**Napište kvalitní `description`.** Agenti AI se spoléhají na pole funkce `description` při rozhodování, kdy nástroj použít. Buďte konkrétní ohledně toho, co nástroj dělá a kdy se má volat.
</Note>
</Accordion>
</AccordionGroup>
<Note>
**Pomocné nástroje za běhu.** `twenty-sdk/utils` znovu exportuje malé pomocné nástroje pro běh, takže handlery nikdy neimportují přímo z `twenty-shared`. Například `isDefined(value)` vrací `false` jak pro `null`, tak pro `undefined` — použijte jej k bezpečnému zúžení volitelných vstupů handleru, které mohou za běhu dorazit jako `null`, i když jsou typované jako `T | undefined`:
```ts
import { isDefined } from 'twenty-sdk/utils';
const handler = async (params: { parentMessageId?: string }) => {
if (isDefined(params.parentMessageId)) {
// params.parentMessageId is narrowed to string here
}
};
```
</Note>
<Note>
**Instalační hooky** — předinstalační, poinstalační a odinstalační handlery — sdílejí toto běhové prostředí, ale deklarují se vlastními funkcemi `define` a nepřebírají nastavení spouštěče (triggeru). Viz [Instalační hooky](/l/cs/developers/extend/apps/config/install-hooks) pro `definePreInstallLogicFunction`, `definePostInstallLogicFunction` a `defineUninstallLogicFunction`.
</Note>
## Vytvoření aktivity časové osy.
Pomocí `createTimelineActivity()` publikujte explicitní doménovou událost z logické funkce. Nejprve definujte událost jako [typ aktivity časové osy](/l/cs/developers/extend/apps/data/timeline-activity-types) a poté typ a objekty adresujte pomocí jejich stabilních univerzálních identifikátorů:
```ts
import { createTimelineActivity } from 'twenty-sdk/logic-function';
await createTimelineActivity({
timelineActivityTypeUniversalIdentifier:
'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
targetObjectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
targetRecordId: postCardId,
happensAt: new Date().toISOString(),
properties: {
deliveryProvider: 'postal-service',
},
});
```
Twenty převede univerzální identifikátory na ID metadat specifická pro instalaci, ověří, že typ aktivity časové osy patří volající aplikaci, a uloží snímek metadat prezentace do nové aktivity.
Požadované vstupy jsou `timelineActivityTypeUniversalIdentifier`, `targetObjectUniversalIdentifier` a `targetRecordId`. Můžete také zadat `happensAt`, `properties` a `workspaceMemberId`. `happensAt` určuje pozici události a zobrazený čas na časové ose; výchozí hodnotou je čas vytvoření.
Chcete-li k události přidružit jiný záznam, zadejte společně `linkedRecordId` a `linkedObjectMetadataUniversalIdentifier`. Jako záložní možnost historického zobrazení můžete navíc zadat `linkedRecordCachedName`:
```ts
await createTimelineActivity({
timelineActivityTypeUniversalIdentifier:
'f4fa646c-6e11-4d8f-a6be-c3b7a2fc7501',
targetObjectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
targetRecordId: postCardId,
linkedObjectMetadataUniversalIdentifier: RECIPIENT_UNIVERSAL_IDENTIFIER,
linkedRecordId: recipientId,
linkedRecordCachedName: recipientName,
});
```
Role logické funkce musí mít oprávnění k zápisu do standardního objektu `timelineActivity`. Explicitní typy událostí ponechte nevázané na `action`; typ vázaný na akci již automaticky přijímá události auditu a jinak by vytvářel duplicitní řádky.
## Typovaní klienti API (twenty-client-sdk)
Balíček `twenty-client-sdk` poskytuje dva typované klienty GraphQL pro práci s Twenty API z vašich logických funkcí a frontendových komponent.
| Klient | Importovat | Koncový bod | Generováno? |
| ------------------- | ---------------------------- | ---------------------------------------------------------------- | ------------------------------ |
| `CoreApiClient` | `twenty-client-sdk/core` | `/graphql` — data pracovního prostoru (záznamy, objekty) | Ano, při vývoji/sestavení |
| `MetadataApiClient` | `twenty-client-sdk/metadata` | `/metadata` — konfigurace pracovního prostoru, nahrávání souborů | Ne, dodává se předem sestavený |
<AccordionGroup>
<Accordion title="CoreApiClient" description="Dotazování a změny dat pracovního prostoru (záznamy, objekty)">
`CoreApiClient` je hlavní klient pro dotazování a mutace dat pracovního prostoru. Generuje se **z vašeho schématu pracovního prostoru** během `yarn twenty dev` nebo `yarn twenty dev:build`, takže je plně typovaný tak, aby odpovídal vašim objektům a polím.
```ts
import { CoreApiClient } from 'twenty-client-sdk/core';
const client = new CoreApiClient();
// Query records
const { companies } = await client.query({
companies: {
edges: {
node: {
id: true,
name: true,
domainName: {
primaryLinkLabel: true,
primaryLinkUrl: true,
},
},
},
},
});
// Create a record
const { createCompany } = await client.mutation({
createCompany: {
__args: {
data: {
name: 'Acme Corp',
},
},
id: true,
name: true,
},
});
```
Klient používá syntaxi výběrové sady (selection-set): předáním `true` zahrnete pole, pro argumenty použijte `__args` a pro relace vnořujte objekty. Získáte plné automatické doplňování a kontrolu typů založené na schématu vašeho pracovního prostoru.
<Note>
**CoreApiClient je generován při vývoji/sestavení.** Pokud jej použijete bez předchozího spuštění `yarn twenty dev` nebo `yarn twenty dev:build`, vyvolá chybu. Generování probíhá automaticky — CLI prozkoumá GraphQL schéma vašeho pracovního prostoru a vygeneruje typovaného klienta pomocí `@genql/cli`.
</Note>
#### Použití CoreSchema pro anotace typů
`CoreSchema` poskytuje typy TypeScriptu odpovídající objektům vašeho pracovního prostoru — hodí se pro typování stavu komponent nebo parametrů funkcí:
```ts
import { CoreApiClient, CoreSchema } from 'twenty-client-sdk/core';
import { useState } from 'react';
const [company, setCompany] = useState<
Pick<CoreSchema.Company, 'id' | 'name'> | undefined
>(undefined);
const client = new CoreApiClient();
const result = await client.query({
company: {
__args: { filter: { position: { eq: 1 } } },
id: true,
name: true,
},
});
setCompany(result.company);
```
</Accordion>
<Accordion title="MetadataApiClient" description="Konfigurace pracovního prostoru, aplikace a nahrávání souborů">
`MetadataApiClient` je dodáván předem sestavený v rámci SDK (není vyžadováno žádné generování). Odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru, aplikace a nahrávání souborů. Přijímá stejnou možnost `runAs` jako `CoreApiClient` — viz [Čí přístup volání používá](#whose-access-a-call-uses).
```ts
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
const metadataClient = new MetadataApiClient();
// List first 10 objects in the workspace
const { objects } = await metadataClient.query({
objects: {
edges: {
node: {
id: true,
nameSingular: true,
namePlural: true,
labelSingular: true,
isCustom: true,
},
},
__args: {
filter: {},
paging: { first: 10 },
},
},
});
```
#### Nahrávání souborů
`MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru:
```ts
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import * as fs from 'fs';
const metadataClient = new MetadataApiClient();
const fileBuffer = fs.readFileSync('./invoice.pdf');
const uploadedFile = await metadataClient.uploadFile(
fileBuffer, // file contents as a Buffer
'invoice.pdf', // filename
'application/pdf', // MIME type
'58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universalIdentifier
);
console.log(uploadedFile);
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
```
| Parametr | Typ | Popis |
| ---------------------------------- | -------- | ------------------------------------------------------------------- |
| `fileBuffer` | `Buffer` | Surový obsah souboru |
| `filename` | `string` | Název souboru (používá se pro ukládání a zobrazení) |
| `contentType` | `string` | Typ MIME (pokud je vynechán, výchozí je `application/octet-stream`) |
| `fieldMetadataUniversalIdentifier` | `string` | `universalIdentifier` pole typu souboru ve vašem objektu |
Hlavní body:
* Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje v jakémkoli pracovním prostoru, kde je vaše aplikace nainstalována.
* Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru.
</Accordion>
</AccordionGroup>
<Note>
Když váš kód běží na Twenty (logické funkce nebo frontendové komponenty), platforma vloží přihlašovací údaje jako proměnné prostředí:
* `TWENTY_API_URL` — Základní URL Twenty API
* `TWENTY_APP_ACCESS_TOKEN` — krátkodobý klíč pro výchozí přístup: roli osoby, když někdo stojí za během, protíná role vaší aplikace; když nikdo nestojí za během, používá se vlastní role vaší aplikace. Osobou je kdokoli, kdo spustil běh v logické funkci, nebo kdokoli, kdo si prohlíží stránku ve frontové komponentě.
* `TWENTY_APP_APPLICATION_ACCESS_TOKEN` — krátkodobý klíč s rozsahem omezeným pouze na vlastní roli vaší aplikace. Pouze pro logické funkce, vždy se do nich vkládá a používá jej `runAs: 'application'`.
Tyto hodnoty klientům **nemusíte** předávat — automaticky je čtou z `process.env` a [Čí přístup volání používá](#whose-access-a-call-uses) popisuje volbu mezi nimi. Vlastní oprávnění vaší aplikace jsou určena rolí deklarovanou pomocí `defineApplicationRole()` (nebo odkazovanou prostřednictvím `defaultRoleUniversalIdentifier` v `application-config.ts`); běh jednající jako osoba nikdy nemůže překročit ani tuto roli, ani roli této osoby.
</Note>

View File

@@ -0,0 +1,61 @@
---
title: Přehled
description: Server-side TypeScript, který běží uvnitř Twenty — spouštěný pomocí HTTP rout, plánů CRON, databázových událostí, nástrojů AI nebo akcí pracovního postupu.
icon: bolt
---
**Logická vrstva** aplikace Twenty je kód, který *běží* — server-side TypeScript handlery reagující na HTTP požadavky, plány CRON a změny záznamů; AI dovednosti a agenti, kteří fungují uvnitř pracovního prostoru; a připojení OAuth, která umožňují vašim funkcím jednat jménem uživatele ve službách třetích stran.
```text
┌─ HTTP route ──┐
│ Cron schedule │
│ Database event │ ┌────────────────────┐
triggers ─┤ AI tool call ├─────▶│ Logic function │
│ Workflow action │ │ (your handler) │
│ Manual exec │ └────────────────────┘
└────────────────────┘ │
▼
┌────────────────────────────┐
│ Twenty API (records) │
│ Third-party API │
│ (via Connection token) │
└────────────────────────────┘
```
## V této části
<CardGroup cols={2}>
<Card title="Logické funkce" icon="bolt" href="/l/cs/developers/extend/apps/logic/logic-functions">
Základní stavební blok — typy spouštěčů, payloady a typovaný klient API.
</Card>
<Card title="Dovednosti a agenti" icon="robot" href="/l/cs/developers/extend/apps/logic/skills-and-agents">
Opakovaně použitelné pokyny pro agenty AI a asistenti s vlastními systémovými prompty.
</Card>
<Card title="Připojení" icon="plug" href="/l/cs/developers/extend/apps/logic/connections">
Přihlašovací údaje OAuth, které vaše aplikace uchovává pro služby třetích stran — Linear, GitHub, Slack a další.
</Card>
<Card title="Úložiště typu klíč–hodnota" icon="database" href="/l/cs/developers/extend/apps/logic/key-value-store">
Udržujte stav mezi spuštěními logických funkcí – cache, kurzory a nároky napříč pracovními prostory.
</Card>
<Card title="Úlohy na pozadí" icon="layer-group" href="/l/cs/developers/extend/apps/logic/background-jobs">
Zařaďte spuštění logické funkce do fronty pro pracovníky, abyste obešli časový limit na jedno spuštění.
</Card>
</CardGroup>
## Přehled typů spouštěčů
Logická funkce volí jeden nebo více spouštěčů — každá z níže uvedených položek je samostatné pole v `defineLogicFunction()`:
| Spouštěč | Kdy se spouští | Nastavení |
| --------------------------- | ----------------------------------------------------------------- | ------------------------------- |
| **HTTP route** | Požadavek dorazí na váš koncový bod `/s/\<path>` | `httpRouteTriggerSettings` |
| **Cron** | CRON výraz se shoduje | `cronTriggerSettings` |
| **Událost databáze** | Záznam v pracovním prostoru je vytvořen, aktualizován nebo smazán | `databaseEventTriggerSettings` |
| **Nástroj AI** | Funkce Twenty AI se rozhodne zavolat vaši funkci | `toolTriggerSettings` |
| **Akce pracovního postupu** | Krok pracovního postupu vyvolá vaši funkci | `workflowActionTriggerSettings` |
Funkce běží v izolovaných sandboxovaných procesech Node.js a přistupují k pracovnímu prostoru přes typovaného klienta API omezeného na roli deklarovanou v [`defineApplication()`](/l/cs/developers/extend/apps/config/application).
<Note>
**Instalační hooky** — kód, který běží před nebo po instalaci — sdílejí toto běhové prostředí, ale používají vlastní funkce `define` a nacházejí se pod [Config → Install Hooks](/l/cs/developers/extend/apps/config/install-hooks).
</Note>

View File

@@ -0,0 +1,187 @@
---
title: Dovednosti a agenti
description: Definujte dovednosti a agenty AI pro svou aplikaci.
icon: robot
---
<Warning>
Dovednosti a agenti jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
</Warning>
Aplikace mohou definovat schopnosti AI, které fungují přímo v pracovním prostoru — znovupoužitelné pokyny pro dovednosti a agenty s vlastními systémovými prompty.
<AccordionGroup>
<Accordion title="defineSkill" description="Definujte dovednosti agentů AI">
Dovednosti definují znovupoužitelné pokyny a schopnosti, které mohou agenti AI používat ve vašem pracovním prostoru. K definování dovedností s vestavěnou validací použijte `defineSkill()`:
```ts src/skills/example-skill.ts
import { defineSkill } from 'twenty-sdk/define';
export default defineSkill({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'sales-outreach',
label: 'Sales Outreach',
description: 'Guides the AI agent through a structured sales outreach process',
icon: 'IconBrain',
content: `You are a sales outreach assistant. When reaching out to a prospect:
1. Research the company and recent news
2. Identify the prospect's role and likely pain points
3. Draft a personalized message referencing specific details
4. Keep the tone professional but conversational`,
});
```
Hlavní body:
* `name` je jedinečný identifikátor dovednosti (doporučuje se kebab-case).
* `label` je uživatelsky čitelný název zobrazovaný v UI.
* `content` obsahuje pokyny dovednosti — je to text, který agent AI používá.
* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI.
* `description` (volitelné) poskytuje doplňující kontext o účelu dovednosti.
</Accordion>
<Accordion title="defineAgent" description="Definujte AI agenty s vlastními prompty">
Agenti jsou asistenti AI, kteří běží ve vašem pracovním prostoru. K vytvoření agentů s vlastním systémovým promptem použijte `defineAgent()`:
```ts src/agents/example-agent.ts
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
name: 'sales-assistant',
label: 'Sales Assistant',
description: 'Helps the sales team draft outreach emails and research prospects',
icon: 'IconRobot',
prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.',
});
```
Hlavní body:
* `name` je jedinečný identifikátor agenta (doporučuje se kebab-case).
* `label` je zobrazovaný název v UI.
* `prompt` je systémový prompt, který definuje chování agenta.
* `description` (volitelné) poskytuje kontext o tom, co agent dělá.
* `icon` (volitelné) nastavuje ikonu zobrazovanou v UI.
* `modelId` (volitelné) přepíše výchozí model AI používaný agentem.
* `responseFormat` (volitelně) určuje tvar výstupu agenta. Výchozí hodnota je `{ type: 'text' }` pro volný text. Použijte `{ type: 'json', schema }` k vynucení strukturovaného výstupu ve formátu JSON.
Ve výchozím nastavení agent vrací volný text. Chcete-li získat strukturovaný výstup, nastavte `responseFormat` na `{ type: 'json' }` a poskytněte `schema`:
```ts src/agents/structured-agent.ts
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: 'c4d5e6f7-a8b9-0123-cdef-456789012345',
name: 'lead-scorer',
label: 'Lead Scorer',
prompt: 'Score the lead and explain your reasoning.',
responseFormat: {
type: 'json',
schema: {
type: 'object',
properties: {
score: { type: 'number', description: 'Lead score from 0 to 100' },
summary: { type: 'string', description: 'Short reasoning for the score' },
},
required: ['score', 'summary'],
additionalProperties: false,
},
},
});
```
Poznámky ke schématu:
* Schéma je plochý objekt: `type` každé vlastnosti musí být primitivní typ (`string`, `number` nebo `boolean`). Vnořené objekty a pole nejsou podporovány.
* `description` (volitelně) u každé vlastnosti navádí model, co má na toto místo doplnit.
* `required` (volitelně) vypisuje vlastnosti, které musí model vždy vrátit.
* `additionalProperties: false` (volitelně) zakáže jakoukoli vlastnost, která není deklarována v `properties`.
</Accordion>
<Accordion title="runAgent" description="Spuštění agenta z logické funkce">
`runAgent()` umožňuje logické funkci spustit jednoho z agentů vaší aplikace (s jeho dovednostmi a nástroji). Identifikujte agenta pomocí `universalIdentifier`, který jste předali do `defineAgent()`. Předejte buď řetězec `prompt`, nebo historii konverzace `messages`, ale ne obojí zároveň:
```ts src/logic-functions/run-enricher.ts
import { runAgent } from 'twenty-sdk/logic-function';
const { result, error, success } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
prompt: 'Enrich House Ad <recordId>: fill empty fields from its listing URL.',
});
```
U vícekrokových botů (Slack, Discord, Teams, …) předejte historii vlákna jako `messages` místo jednoho `prompt`:
```ts src/logic-functions/reply-in-thread.ts
import { runAgent } from 'twenty-sdk/logic-function';
const { result, error, success } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
messages: [
{ role: 'user', content: 'Who owns Acme?' },
{ role: 'assistant', content: 'Sarah owns the Acme account.' },
{ role: 'user', content: 'What was the last touchpoint?' },
],
});
```
Hlavní body:
* Zadejte **přesně jednu** z možností: `prompt` (řetězec) nebo `messages` (1 až 100 položek
`{ role: 'user' | 'assistant', content: string }`).
* Agent běží **synchronně** a může sám číst/aktualizovat záznamy pomocí vlastních nástrojů — `runAgent()` vrátí výsledek až po dokončení běhu.
* Aplikace může spouštět pouze své vlastní agenty.
* [Výchozí role](/l/cs/developers/extend/apps/config/roles) aplikace musí udělovat příznak oprávnění `AI` — přidejte `SystemPermissionFlag.AI` do `permissionFlagUniversalIdentifiers` (nebo nastavte `canAccessAllTools: true`).
Bez něj `runAgent()` selže s chybou oprávnění.
* Nastavte u logické funkce velkorysou hodnotu `timeoutSeconds` — běh agenta může trvat několik sekund.
* `success` je `true` a `result` není null po dokončení běhu; při chybě je `success` `false`, `result` je `null` a `error` obsahuje důvod (například když během běhu workspace vyčerpal AI kredity).
```ts src/roles/default-role.ts
import { defineApplicationRole, SystemPermissionFlag } from 'twenty-sdk/define';
export default defineApplicationRole({
universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
label: 'Default function role',
// runAgent() requires the AI permission flag on the app's default role.
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.AI],
});
```
<Warning>
**Vyhněte se smyčkám:** pokud voláte `runAgent()` z databázového triggeru `*.updated` a agent aktualizuje stejný záznam, omezte trigger pomocí `updatedFields` na pole, do kterého agent nikdy nezapisuje (např. zdrojovou URL), nebo před voláním `runAgent()` zkontrolujte, zda je některé cílové pole stále prázdné.
</Warning>
### Spuštění jménem člena pracovního prostoru
Předejte `runAsWorkspaceMemberId`, když je spuštění vyvoláno osobou — například chatbotem odpovídajícím na zprávu — aby agent jednal jako tento člen, nikoli jako aplikace:
```ts src/logic-functions/answer-question.ts
const { result } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
prompt: 'How many open opportunities do we have?',
runAsWorkspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7',
});
```
Spuštění pak jedná s **vlastní rolí člena**: může dělat vše, co může tento člen a nic navíc, záznamy, které vytváří, jsou mu připsány a uplatňují se na něj jeho oprávnění na úrovni řádků. Role agenta se toho neúčastní — je výchozí rolí aplikace pro spuštění, za nimiž nestojí žádný uživatel. Toto pole vynechejte u autonomních spuštění (plánované úlohy, spouštěče databázových událostí): ta si ponechají vlastní roli agenta. Aplikace, která jedná, zůstává kvůli doložení původu navázaná na kontext spuštění, aniž by zužovala oprávnění člena.
Vaše aplikace je zodpovědná za mapování osoby, která spuštění vyvolala, na člena pracovního prostoru. Pojmenování člena vyžaduje přístupový token aplikace a to, koho může token
pojmenovat, závisí na tom, zda je navázán na uživatele:
* Token, který **nemá přiřazeného žádného uživatele**, může pojmenovat libovolného člena. Logická funkce běží
s takovým tokenem a totéž platí pro tokeny vytvořené prostřednictvím `client_credentials` nebo z
klíče API.
* Token vydaný **jménem uživatele**, jaký obdrží front-endová komponenta, smí
pojmenovat pouze člena daného uživatele.
Cokoli jiného — běžná uživatelská relace, API klíč bez tokenu aplikace — vůbec
nesmí pojmenovat žádného člena.
<Warning>
`runAgent()` vyvolá výjimku, pokud nelze člena pracovního prostoru určit — neznámý nebo odebraný člen, případně člen bez role. Nikdy se nevrací zpět k
vlastní roli agenta, protože to by udělilo více oprávnění, než o která volající
požádal.
</Warning>
</Accordion>
</AccordionGroup>

View File

@@ -0,0 +1,131 @@
---
title: CLI
description: příkazy `yarn twenty` pro spouštění funkcí, streamování logů, správu instalací aplikací a přepínání vzdálených serverů.
icon: terminal
---
CLI `yarn twenty` je vaše rozhraní ke všemu, co souvisí s aplikací. Úplný seznam příkazů:
| Příkaz | K čemu slouží | Zdokumentováno v |
| ----------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `dev` | Sleduje zdrojové soubory a živě synchronizuje změny | [Rychlý start](/l/cs/developers/extend/apps/getting-started/quick-start) |
| `plán` | Náhled změn metadat bez jejich aplikování | [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery#previewing-changes-plan) |
| `použít` | Aplikovat změny metadat po zobrazení plánu | [Synchronizace a obnovení](/l/cs/developers/extend/apps/operations/sync-and-recovery) |
| `dev:build` | Zkompilovat aplikaci a vygenerovat API klienta (`--tarball` pro zabalení do `.tgz`) | [Publikování](/l/cs/developers/extend/apps/operations/publishing) |
| `dev:typecheck` | Spustit kontrolu typů TypeScriptu | [Testování](/l/cs/developers/extend/apps/operations/testing) |
| `dev:add` | Vytvořit kostru nové entity | [Scaffolding](/l/cs/developers/extend/apps/getting-started/scaffolding) |
| `dev:generate-client` | Znovu vygenerovat typovaného API klienta | tato stránka |
| `dev:function:exec` / `dev:function:logs` | Spustit funkce a streamovat jejich logy | tato stránka |
| `dev:translations-extract` | Extrahovat přeložitelné řetězce do katalogů v `locales/` | [Překlady](/l/cs/developers/extend/apps/operations/translations) |
| `dev:catalog-sync` | Spustit synchronizaci katalogu tržiště | [Publikování](/l/cs/developers/extend/apps/operations/publishing#how-marketplace-discovery-works) |
| `app:publish` / `app:install` / `app:uninstall` | Životní cyklus vydání | [Publikování](/l/cs/developers/extend/apps/operations/publishing) a tato stránka |
| `docker:*` | Spravovat kontejner lokálního serveru Twenty | [Lokální server](/l/cs/developers/extend/apps/getting-started/local-server) |
| `remote:*` | Spravovat připojení k serveru | tato stránka |
Každý příkaz přijímá `-r, --remote \<name>` pro zacílení na konkrétní vzdálené připojení místo výchozího.
## Spouštění funkcí (`yarn twenty dev:function:exec`)
Spusťte logickou funkci ručně bez vyvolání přes HTTP, cron nebo databázovou událost:
```bash filename="Terminal"
# Execute by function name
yarn twenty dev:function:exec -n create-new-post-card
# Execute by universalIdentifier
yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
# Pass a JSON payload
yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}'
# Execute the install and uninstall hooks
yarn twenty dev:function:exec --postInstall
yarn twenty dev:function:exec --preInstall
yarn twenty dev:function:exec --uninstall
```
## Zobrazení logů funkcí (`yarn twenty dev:function:logs`)
Streamujte výstupní logy běhu logických funkcí vaší aplikace:
```bash filename="Terminal"
# Stream all function logs
yarn twenty dev:function:logs
# Filter by function name
yarn twenty dev:function:logs -n create-new-post-card
# Filter by universalIdentifier
yarn twenty dev:function:logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
```
<Note>
To se liší od `yarn twenty docker:logs`, který zobrazuje logy kontejneru Docker. `yarn twenty dev:function:logs` zobrazuje logy běhu funkcí vaší aplikace ze serveru Twenty.
</Note>
## Generování typovaného klienta (`yarn twenty dev:generate-client`)
Znovu vygenerujte typovaného klienta API (`twenty-client-sdk`) ze schématu aktivního vzdáleného serveru, bez sestavování nebo synchronizace aplikace. Použijte jej k získání typovaného klienta v libovolném projektu – například backendové služby v samostatném repozitáři – který komunikuje s vaší instancí Twenty:
```bash filename="Terminal"
# In your project (no Twenty app definition required)
yarn add twenty-sdk twenty-client-sdk
# Connect to the Twenty instance to generate the client from
yarn twenty remote:add
# Generate the typed client into node_modules/twenty-client-sdk
yarn twenty dev:generate-client
```
Poté klienta importujte ve svém kódu:
```typescript
import { CoreApiClient } from 'twenty-client-sdk/core';
```
Spusťte příkaz znovu pokaždé, když se změní váš datový model, abyste aktualizovali vygenerované typy.
<Note>
Klient je vygenerován uvnitř `node_modules`, takže není verzován spolu s vaším kódem. Spusťte `yarn twenty dev:generate-client` po každé instalaci (například ve skriptu `postinstall` nebo v CI).
</Note>
## Odinstalace aplikace (`yarn twenty app:uninstall`)
Odeberte svou aplikaci z aktivního pracovního prostoru:
```bash filename="Terminal"
yarn twenty app:uninstall
# Skip the confirmation prompt
yarn twenty app:uninstall --yes
```
## Správa vzdálených serverů
**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat.
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote:add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote:add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote:add --url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote:list
# Set the active remote
yarn twenty remote:use <name>
# Check that the active remote's authentication is still valid
yarn twenty remote:status
# Remove a remote
yarn twenty remote:remove <name>
```
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.

View File

@@ -0,0 +1,32 @@
---
title: Přehled
description: Sestavte, otestujte a doručte svou aplikaci — příkazy CLI, integrační testy, CI a publikování na server nebo do npm.
icon: rocket
---
**Provozní vrstva** je všechno, co děláte *na* své aplikaci, nikoli *pomocí* ní: spouštění příkazů CLI, provádění integračních testů proti reálnému serveru Twenty, konfigurace CI a vydávání verzí — buď jako tarball nasazený na jednom serveru, nebo jako balíček npm uvedený v Marketplace.
```text
develop ─▶ test ─▶ build ─▶ deploy / publish
─────── ──── ───── ─────────────────
yarn yarn yarn yarn twenty app:publish --private (tarball → one server)
twenty test twenty
dev dev:build yarn twenty app:publish (npm → marketplace)
```
## V této části
<CardGroup cols={2}>
<Card title="CLI" icon="terminal" href="/l/cs/developers/extend/apps/operations/cli">
Referenční přehled `yarn twenty` — exec, logs, uninstall, remotes.
</Card>
<Card title="Synchronizace a obnovení" icon="compass" href="/l/cs/developers/extend/apps/operations/sync-and-recovery">
Který příkaz kdy použít, jak číst diff synchronizace a postup obnovy.
</Card>
<Card title="Testování" icon="flask" href="/l/cs/developers/extend/apps/operations/testing">
Nastavení Vitestu, integrační testy, kontrola typů, workflow CI.
</Card>
<Card title="Publikování" icon="upload" href="/l/cs/developers/extend/apps/operations/publishing">
Sestavit, nasadit tarball, publikovat do npm, nainstalovat.
</Card>
</CardGroup>

View File

@@ -0,0 +1,293 @@
---
title: Publikování
icon: upload
description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte interně.
---
## Přehled
Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/getting-started/concepts), máte dvě cesty, jak ji distribuovat:
* **Nasaďte tarball** — nahrajte svou aplikaci přímo na konkrétní server Twenty pro interní nebo soukromé použití.
* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat.
Obě cesty začínají stejným krokem **build**.
## Sestavení vaší aplikace
Spusťte příkaz build ke zkompilování své aplikace a k vygenerování souboru `manifest.json` připraveného k distribuci:
```bash filename="Terminal"
yarn twenty dev:build
```
Tím se zkompilují zdrojové soubory TypeScriptu, transpilují logické funkce a frontendové komponenty a vše se zapíše do `.twenty/output/`. Přidejte `--tarball`, abyste také vytvořili balíček `.tgz` pro ruční distribuci nebo příkaz publish.
## Nasazení na server (tarball)
U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty.
### Předpoklady
Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`.
Přidat vzdálený cíl:
```bash filename="Terminal"
yarn twenty remote:add --url https://your-twenty-server.com --as production
```
### Nasazení
Sestavte a nahrajte svou aplikaci na server v jednom kroku:
```bash filename="Terminal"
yarn twenty app:publish --private
# To deploy to a specific remote:
# yarn twenty app:publish --private --remote production
```
### Sdílení nasazené aplikace
Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci:
1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci
2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení**
3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace
Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru.
### Správa verzí
Při aktualizaci již nasazené tarballové aplikace server vyžaduje, aby hodnota `version` v `package.json` byla **přísně vyšší** (podle řazení [semver](https://semver.org)) než aktuálně nasazená verze. Opětovné nasazení stejné verze nebo odeslání nižší verze je odmítnuto ještě před uložením tarballu — v CLI uvidíte chybu `VERSION_ALREADY_EXISTS`.
Chcete-li vydat aktualizaci:
1. Zvyšte hodnotu pole `version` v souboru `package.json` (např. `1.2.3` → `1.2.4`, `1.3.0` nebo `2.0.0`)
2. Spusťte `yarn twenty app:publish --private` (nebo `yarn twenty app:publish --private --remote production`)
3. Pracovní prostory, které mají aplikaci nainstalovanou a mají pro ni povolené automatické aktualizace (na kartě Nastavení aplikace), jsou na pozadí aktualizovány automaticky; ostatní uvidí dostupnou aktualizaci ve svém nastavení
<Note>
Předběžné tagy fungují podle očekávání: zvýšení z `1.0.0-rc.1` → `1.0.0-rc.2` je povoleno a finální vydání jako `1.0.0` je správně rozpoznáno jako vyšší než `1.0.0-rc.5`. Verze v `package.json` musí být platným řetězcem semver.
</Note>
{/* TODO: add screenshot of the Upgrade button */}
### Kompatibilita verze serveru
Pokud vaše aplikace používá funkci zavedenou v konkrétní verzi serveru Twenty (například poskytovatelé OAuth přidaní ve verzi 2.3.0), měli byste deklarovat minimální verzi serveru, kterou vaše aplikace vyžaduje, pomocí pole `engines.twenty` v `package.json`:
```json filename="package.json"
{
"name": "twenty-my-app",
"version": "1.0.0",
"engines": {
"node": "^24.5.0",
"twenty": ">=2.3.0"
}
}
```
Hodnota je standardní [rozsah SemVer](https://github.com/npm/node-semver#ranges). Běžné vzory:
| Rozsah | Význam |
| ---------------------------------- | ---------------------------------------------- |
| `>=2.3.0` | Jakýkoli server od verze 2.3.0 výše |
| `>=2.3.0 \<3.0.0` | 2.3.0 nebo novější, ale pod další hlavní verzí |
| `^2.3.0` | Stejné jako `>=2.3.0 \<3.0.0` |
**Co se děje při nasazení a instalaci:**
* Pokud je `engines.twenty` nastaveno a verze cílového serveru nevyhovuje rozsahu, nasazení (nahrání tarballu) nebo instalace je odmítnuto chybou `SERVER_VERSION_INCOMPATIBLE` a zprávou, která uvádí jak požadovaný rozsah, tak skutečnou verzi serveru.
* Pokud `engines.twenty` **není nastaveno**, aplikace je přijata na jakékoli verzi serveru (zpětně kompatibilní se stávajícími aplikacemi).
* Pokud server nemá nakonfigurované `APP_VERSION`, kontrola se přeskočí.
<Note>
Server je rozhodující autoritou — ověřuje `engines.twenty` jak při nahrání tarballu, tak při instalaci do pracovního prostoru. Pokud nasazujete tarball mimo standardní proces nebo instalujete z marketplace, server přesto vynucuje kompatibilitu.
</Note>
## Automatizované CI/CD (předpřipravené workflowy)
Aplikace vygenerované pomocí `create-twenty-app` jsou hned připravené se třemi workflowy GitHub Actions ve složce `.github/workflows/`. CI běží bez jakéhokoli nastavování, CD vyžaduje jediný secret a publikování na npm vyžaduje jednorázové nastavení npm trusted-publisher.
### CI — `ci.yml`
Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů.
**K čemu slouží:**
1. Provede checkout zdrojového kódu vaší aplikace.
2. Spustí izolovanou testovací instanci Twenty pomocí složené akce `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` (ekvivalent v CI k `yarn twenty docker:start --test`).
3. Povolí Corepack, nastaví Node.js podle vašeho `.nvmrc` a nainstaluje závislosti pomocí `yarn install --immutable`.
4. Spustí `yarn test` a předá `TWENTY_API_URL` a `TWENTY_API_KEY` ze spuštěné instance, aby vaše testy mohly komunikovat se skutečným serverem.
**Konfigurační volby:**
* `TWENTY_VERSION` (env, výchozí hodnota `latest`) — uzamkněte v CI používanou verzi serveru Twenty úpravou této hodnoty v `ci.yml`.
* Souběžné běhy jsou seskupeny podle `github.ref` a při nových pushích ruší právě probíhající běhy.
Nejsou potřeba žádné secrety — testovací instance je efemérní a existuje pouze po dobu běhu úlohy.
### CD — `cd.yml`
Nasazuje vaši aplikaci na nakonfigurovaný server Twenty při každém pushi do `main` a volitelně také z pull requestu, pokud je přidán štítek `deploy`.
**K čemu slouží:**
1. Provede checkout headu PR (u označených PR) nebo pushnutého commitu.
2. Spustí `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — ekvivalent v CI k `yarn twenty app:publish --private`.
3. Spustí `twentyhq/twenty/.github/actions/install-twenty-app@main`, aby se nově nasazená verze nainstalovala do cílového pracovního prostoru.
**Požadovaná konfigurace:**
| Nastavení | Kde | Účel |
| ----------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `TWENTY_DEPLOY_URL` | `env` v `cd.yml` (výchozí `http://localhost:3000`) | Server Twenty, na který se nasazuje. Před prvním použitím to změňte na skutečnou URL vašeho serveru. |
| `TWENTY_DEPLOY_API_KEY` | GitHub repozitář **Settings → Secrets and variables → Actions** | API klíč s oprávněním k nasazení na cílovém serveru. |
<Note>
Výchozí `TWENTY_DEPLOY_URL` `http://localhost:3000` je pouze zástupná hodnota — z runneru hostovaného GitHubem tato adresa nebude dosažitelná. Před povolením CD ji aktualizujte na veřejnou URL vašeho serveru (nebo použijte self-hosted runner s přístupem do sítě).
</Note>
**Spuštění náhledového nasazení z PR:**
Přidejte k pull requestu štítek `deploy`. Podmínka `if:` v `cd.yml` spustí úlohu pro dané PR s použitím head commitu PR, což vám umožní ověřit změnu na cílovém serveru před sloučením.
### Publish — `publish.yml`
Publikuje vaši aplikaci na npm s doloženým původem, když odešlete tag verze (např. `v1.0.0`), nebo když workflow spustíte ručně na kartě Actions.
**K čemu slouží:**
1. Načte repozitář vaší aplikace, nastaví Node.js a aktualizuje npm (důvěryhodné publikování vyžaduje npm 11.5.1 nebo novější).
2. Spustí `yarn twenty app:publish`, který sestaví aplikaci a publikuje `.twenty/output` na npm. V CI automaticky přidá `--provenance` a `--access public`, takže ve workflowu nejsou potřeba žádné přepínače.
**Jednorázové nastavení:**
Na npmjs.com otevřete svůj balíček > **Settings → Trusted Publisher** a zaregistrujte tento repozitář s workflowem `publish.yml` (viz [dokumentaci k důvěryhodnému publikování na npm](https://docs.npmjs.com/trusted-publishers)). Publikování s provenance potvrzuje, který repozitář na GitHubu balíček sestavil, a zároveň tak uplatňujete vlastnictví své aplikace na tržišti Twenty.
<Note>
npm přijímá údaje o původu pouze z **veřejných** zdrojových repozitářů. Pokud publikujete ze soukromého repozitáře, npm odmítne balíček údajů o původu OIDC s chybou `E422 ... Unsupported GitHub Actions source repository visibility: "private"` error. Chcete-li publikovat ze soukromého repozitáře, vypněte prokazování původu nastavením `TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'` v `env` publikačního kroku (ve vygenerovaném `publish.yml` je uveden zakomentovaný tip):
```yaml filename=".github/workflows/publish.yml"
- name: Publish to npm
env:
TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'
run: yarn twenty app:publish
```
</Note>
### Připnutí verzí znovupoužitelných akcí
Workflowy `ci.yml` a `cd.yml` odkazují na znovupoužitelné akce na `@main`, takže aktualizace akcí v repozitáři `twentyhq/twenty` se přeberou automaticky. Pokud chcete deterministická sestavení, nahraďte `@main` v každém řádku `uses:` za commit SHA nebo tag vydání.
## Publikování na npm
Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketplace Twenty. Jakýkoli pracovní prostor Twenty může procházet, instalovat a aktualizovat aplikace z Marketplace přímo z UI.
### Požadavky
* Účet na [npm](https://www.npmjs.com)
* Klíčové slovo `twenty-app` ve vašem poli `keywords` v souboru `package.json` (přidejte ho ručně — ve výchozím nastavení není zahrnuto v šabloně `create-twenty-app`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Metadata tržiště
Konfigurace `defineApplication()` podporuje volitelná pole, která určují, jak se vaše aplikace zobrazuje v tržišti. Použijte `logo` a `galleryImages` k odkazování na obrázky ze složky `public/`:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
logo: 'public/logo.png',
galleryImages: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
Podívejte se na [sekci defineApplication](/l/cs/developers/extend/apps/config/application#marketplace-metadata) na stránce Building Apps pro úplný seznam polí tržiště (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` atd.).
#### Doporučené rozměry obrázků v galerii
Tržiště zobrazuje `galleryImages` v pevném kontejneru s poměrem stran `8:5` (například `1600×1000 px`).
<Note>
Obrázky v galerii libovolného poměru stran se zobrazují celé a nikdy se neořezávají, ale cokoli výrazně vyššího nebo užšího než `8:5` bude mít po stranách prázdné pruhy.
</Note>
#### Limit velikosti obrázku
Soubor `logo` a každý soubor v `galleryImages` nesmí překročit **10 MB**. Větší soubory jsou při přesunu vašich publikovaných souborů na servery marketplace vynechány, takže se nezobrazí.
### Publikování
```bash filename="Terminal"
yarn twenty app:publish
```
Chcete-li publikovat pod konkrétním dist-tagem (např. `beta` nebo `next`):
```bash filename="Terminal"
yarn twenty app:publish --tag beta
```
### Jak funguje objevování v tržišti
Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**.
Synchronizaci můžete spustit okamžitě místo čekání:
```bash filename="Terminal"
yarn twenty dev:catalog-sync
# To target a specific remote:
# yarn twenty dev:catalog-sync --remote production
```
Metadata zobrazená na tržišti pocházejí z vaší konfigurace `defineApplication()` — viz výše [Metadata tržiště](#marketplace-metadata).
<Note>
Pokud vaše aplikace nedefinuje `aboutDescription` v `defineApplication()`, tržiště automaticky použije soubor `README.md` vašeho balíčku z npm jako obsah stránky O aplikaci. To znamená, že můžete spravovat jediný soubor README jak pro npm, tak pro tržiště Twenty. Pokud chcete v tržišti jiný popis, explicitně nastavte `aboutDescription`.
</Note>
### Publikování pomocí CI
Vygenerované workflow `publish.yml` popsané výše publikuje na npm automaticky při verzovacích tagách, s provenance. Protože `yarn twenty app:publish` při běhu v CI přidá `--provenance` a `--access public` za vás, workflow nepotřebuje žádné přepínače npm — pouze jednorázové nastavení důvěryhodného vydavatele.
Pro jiné CI systémy (GitLab CI, CircleCI atd.) spusťte `yarn install` a poté `yarn twenty app:publish`. Provenance se vydává, pokud prostředí umí vytvořit token OIDC, a v opačném případě se automaticky vynechá.
<Note>
**npm provenance** přidává k vašemu záznamu na npm odznak důvěryhodnosti a umožňuje uživatelům ověřit, že balíček byl sestaven z konkrétního commitu ve veřejné CI pipeline. Také vám umožňuje uplatnit vlastnictví vaší aplikace na tržišti Twenty. Podrobnosti najdete v [dokumentaci k npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Note>
## Instalace aplikací
Jakmile je aplikace publikována (npm) nebo nasazena (tarball), mohou ji pracovní prostory nainstalovat prostřednictvím uživatelského rozhraní.
Přejděte na stránku **Nastavení > Aplikace** v Twenty, kde lze procházet a instalovat jak aplikace z tržiště, tak aplikace nasazené jako tarball.
{/* TODO: add screenshot of the UI when the app is registered */}
Aplikace můžete nainstalovat také z příkazového řádku:
```bash filename="Terminal"
yarn twenty app:install
```
<Note>
Server při instalaci vynucuje verzování semver a zrcadlí pravidla pro nasazení:
* Instalace stejné verze, která je již nainstalována ve vašem pracovním prostoru, je odmítnuta s chybou `APP_ALREADY_INSTALLED`.
* Instalace nižší verze, než je aktuálně nainstalovaná, je odmítnuta s chybou `CANNOT_DOWNGRADE_APPLICATION`.
K instalaci novější verze ji nejprve nasaďte nebo publikujte, poté znovu spusťte `yarn twenty app:install`.
</Note>

View File

@@ -0,0 +1,126 @@
---
title: Synchronizace a obnovení
description: Který příkaz kdy použít, jak číst výstup synchronizace a jak postupovat po jednotlivých krocích při obnově v případě odchýlení lokálních metadat — ještě předtím, než sáhnete k úplnému resetu.
icon: compass
---
Lokální vývoj aplikací se točí kolem **synchronizace**: CLI znovu sestaví váš manifest a server aplikuje pouze rozdíly mezi ním a metadaty, která už jsou ve vašem pracovním prostoru. Tato stránka popisuje, po kterém příkazu sáhnout, jak číst, co synchronizace změnila, a co dělat — v daném pořadí — když lokální stav vypadá nekonzistentně.
## Jaký příkaz, kdy
<Note>
Pro každodenní lokální iteraci téměř vždy chcete `yarn twenty dev`. Nasazování a publikování slouží k vydávání verzí, **ne** pro lokální vývojovou smyčku.
</Note>
| Chcete… | Příkaz | Poznámky |
| --------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Iterujte lokálně s živou synchronizací | `yarn twenty dev` | Sleduje vaše soubory a při každé změně spustí synchronizaci. |
| Jednorázová synchronizace a ukončení (CI, skripty, hooky) | `yarn twenty apply` | Provede jedno sestavení + synchronizaci a skončí. Přidejte `--force`, abyste přeskočili potvrzení destruktivní změny. |
| Náhled změn **bez jejich aplikování** | `yarn twenty plan` | Spočítá a vypíše rozdíly; nic nezapisuje. |
| Odebrat aplikaci z pracovního prostoru | `yarn twenty app:uninstall` | Přidejte `--yes` pro přeskočení výzvy. |
| Odeslat tarball na server | `yarn twenty app:publish --private` | Vyžaduje **přísně vyšší** verzi v `package.json` — viz [Publikování](/l/cs/developers/extend/apps/operations/publishing). |
| Publikovat na marketplace (npm) | `yarn twenty app:publish` | — |
| Nainstalovat / aktualizovat nasazenou verzi | `yarn twenty app:install` | Nainstaluje aktuálně nasazenou verzi. |
| Vymazat lokální server a začít znovu | `yarn twenty docker:reset` | Smaže **všechna** lokální data — krajní řešení. |
<Note>
`yarn twenty dev --once` a `yarn twenty dev --once --dry-run` stále fungují jako zastaralé aliasy pro `yarn twenty apply` a `yarn twenty plan`.
</Note>
### Lokální synchronizace nevyžaduje zvýšení verze
Pravidlo striktně rostoucí `version` (`VERSION_ALREADY_EXISTS` při nasazení, `APP_ALREADY_INSTALLED` / `CANNOT_DOWNGRADE_APPLICATION` při instalaci) platí pro **`app:publish` / `app:install`** — cestu vydání. `yarn twenty dev` synchronizuje váš manifest na místě a nikdy nevyžaduje změnu verze, takže kvůli iteraci nemusíte sahat na `package.json`. Pokud zvyšujete verzi, abyste otestovali lokální změnu, používáte cestu vydání, i když chcete vývojovou smyčku.
## Čtení výstupu synchronizace
Každá synchronizace vypíše změny metadat, které aplikovala (nebo by aplikovala s `plan`), ve stylu Terraformu — jeden blok na entitu s jejími atributy a poté souhrnný řádek:
```text filename="Terminal"
# objectMetadata "rocket" will be created
+ icon = "IconRocket"
+ labelSingular = "Rocket"
+ ...
# fieldMetadata "launchedAt" will be updated
~ isNullable = false -> true
Plan: 2 to add, 1 to change, 1 to destroy.
✓ Synced My App (4 files)
```
To je váš první diagnostický nástroj: přesně ukazuje, které objekty, pole a rozložení se změnily, takže si můžete ověřit, že synchronizace udělala to, co jste očekávali, ještě před kontrolou v UI.
Destruktivní změny (`to destroy`) jsou uvedeny s tím, co odstraňují (např. `objectMetadata "auditNote" — drops the table and all its rows`) a vyžadují interaktivní potvrzení, nebo `--force` ve skriptech.
Když synchronizace selže na jedné entitě, chyba uvede problematickou entitu a její `universalIdentifier`, například:
```text
Migration action 'create' for 'fieldMetadata' (universalIdentifier: 2020...4337) failed
```
Tento identifikátor použijte k nalezení entity ve vašem manifestu (a případně v pracovním prostoru) místo hádání, která je v konfliktu.
## Náhled změn (plan)
`yarn twenty plan` sestaví váš manifest, požádá server o migrační plán a vypíše ho — **aniž by cokoli aplikoval**. Je to bezpečný způsob, jak si předem zodpovědět otázku „co by tato synchronizace změnila?“ ještě předtím, než se k ní zavážete.
```bash filename="Terminal"
yarn twenty plan
```
```text filename="Terminal"
Building manifest...
Computing metadata plan (read-only, nothing will be applied)...
# fieldMetadata "crewCapacity" will be created
+ ...
Plan: 1 to add, 1 to change, 0 to destroy.
✓ Plan complete for My App — no changes were applied
```
Plán:
* **Nic nezapisuje** — žádná migrace metadat, žádná aktualizace záznamu aplikace, žádné změny výchozí role / karty a žádná generace API klienta.
* Vrací **stejný diff**, jaký by aplikovala skutečná synchronizace, takže můžete předem zkontrolovat vytvořené / aktualizované / smazané entity.
* Je užitečný před rizikovou změnou, při kontrole změny vygenerované pomocí AI nebo ve skriptu, který má selhat, pokud se má provést neočekávaná změna.
<Note>
Plán zobrazuje pouze náhled změn **metadat** a vyžaduje, aby byla aplikace alespoň jednou synchronizovaná (aby o ní pracovní prostor věděl). Pokud jej spustíte proti aplikaci, která nikdy nebyla synchronizovaná, server ohlásí, že aplikace není nainstalovaná — nejprve jednou spusťte `yarn twenty dev`.
</Note>
## Postup obnovy
Když lokální metadata vypadají špatně, postupujte v tomto pořadí a zastavte se, jakmile se problém vyřeší. Každý další krok je rušivější než ten předchozí.
1. **Znovu synchronizujte.** Znovu spusťte `yarn twenty apply`. Synchronizace jsou idempotentní — znovu spuštěný čistý manifest je bezpečný a často vyřeší přechodný problém.
2. **Prohlédněte si plán.** Spusťte `yarn twenty plan`, abyste přesně viděli, co chce další synchronizace změnit, aniž by to aplikovala.
3. **Přečtěte si pojmenovanou chybu.** Pokud synchronizace selže, poznamenejte si typ metadat a `universalIdentifier` ve zprávě (viz výše) a tuto entitu najděte ve svém manifestu. Konflikt obvykle ukazuje na duplicitní nebo znovu použitý identifikátor.
4. **Odinstalujte a znovu nainstalujte.** `yarn twenty app:uninstall`, poté znovu synchronizujte (`yarn twenty dev`). Tím znovu vybudujete metadata aplikace z čistého stavu, zatímco zbytek vašeho pracovního prostoru zůstane nedotčený.
5. **Úplný reset (krajní řešení).** `yarn twenty docker:reset`, poté znovu naplňte data a synchronizujte.
<Warning>
`yarn twenty docker:reset` smaže **veškerá** data ve vaší lokální instanci — každý pracovní prostor, záznam i aplikaci. Použijte jej až tehdy, když selžou předchozí kroky.
</Warning>
<Note>
Nastala chyba v metadatech? Prosíme, [vytvořte issue](https://github.com/twentyhq/twenty/issues/new/choose) a přiložte chybovou zprávu selhané migrace (s typem metadat a `universalIdentifier`), výstup `Metadata changes` ze synchronizace a příkazy, které jste spustili.
</Note>
## Vyhněte se souběžným synchronizacím v jednom pracovním prostoru
Synchronizace aplikuje migrace metadat. Spouštění několika synchronizačních, nasazovacích nebo instalačních operací proti **stejnému pracovnímu prostoru ve stejnou dobu** — například z víc terminálů nebo od více AI agentů iterujících paralelně — může tyto migrace prokládat a zanechat metadata v částečně aplikovaném stavu.
Server serializuje synchronizace pro každý pracovní prostor, aby tomu zabránil, ale přesto byste citlivé operace s metadaty měli směrovat přes **jeden jediný** proces místo toho, abyste je spouštěli souběžně. Pokud orchestrujete vývoj s více agenty, směrujte jejich volání sync/deploy/install přes jednu frontu, aby vždy běžel jen jeden proces.
## Rozlišení typů selhání
Když se něco pokazí, diff metadat a pojmenované chyby vám umožní lokalizovat selhání:
* **Chyba sestavení manifestu** — CLI selže ještě před synchronizací (`MANIFEST_BUILD_FAILED`, `TYPECHECK_FAILED`); opravte zdrojový kód své aplikace.
* **Chyba synchronizace / migrace** — sestavení proběhne úspěšně, ale aplikování diffu selže a uvede entitu a `universalIdentifier`; opravte konfliktní metadata.
* **Chyba velikosti závislostí** — synchronizace nebo instalace selže, protože produkční `dependencies` aplikace jsou příliš velké na to, aby je bylo možné nainstalovat jako runtime vrstvu (`LOGIC_FUNCTION_DEPENDENCIES_SIZE_EXCEEDED`); přesuňte balíčky, které vaše logické funkce za běhu neimportují (UI knihovny, vývojářské nástroje), do `devDependencies`.
* **Chyba za běhu aplikačního kódu** — synchronizace proběhne úspěšně, ale vaše logické funkce nebo komponenty se za běhu chovají nesprávně; zkontrolujte [protokoly funkcí](/l/cs/developers/extend/apps/operations/cli).
* **Lokální stav instance** — neplatí nic z výše uvedeného a pracovní prostor stále vypadá chybně; pokračujte dolů po žebříčku obnovy.

View File

@@ -0,0 +1,264 @@
---
title: Testování
description: Nastavení Vitestu, integrační testy proti reálnému serveru Twenty, kontrola typů a CI s GitHub Actions.
icon: flask
---
SDK poskytuje programová rozhraní, která vám umožní z testovacího kódu aplikaci sestavit, nasadit, nainstalovat a odinstalovat. V kombinaci s [Vitest](https://vitest.dev/) a typovanými klienty API můžete psát integrační testy, které ověří, že vaše aplikace funguje end-to-end proti reálnému serveru Twenty.
## Používání balíčků npm
Ve své aplikaci můžete nainstalovat a používat libovolný balíček npm. Logické funkce i frontendové komponenty se bundlují pomocí [esbuild](https://esbuild.github.io/), který vloží jejich závislosti přímo do výstupu — za běhu nejsou potřeba žádné `node_modules`.
### Instalace balíčku
```bash filename="Terminal"
yarn add axios
```
Poté jej importujte ve svém kódu:
```ts src/logic-functions/fetch-data.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import axios from 'axios';
const handler = async (): Promise<any> => {
const { data } = await axios.get('https://api.example.com/data');
return { data };
};
export default defineLogicFunction({
universalIdentifier: '...',
name: 'fetch-data',
description: 'Fetches data from an external API',
timeoutSeconds: 10,
handler,
});
```
Stejně to funguje i pro frontendové komponenty:
```tsx src/front-components/chart.tsx
import { defineFrontComponent } from 'twenty-sdk/define';
import { format } from 'date-fns';
const DateWidget = () => {
return <p>Today is {format(new Date(), 'MMMM do, yyyy')}</p>;
};
export default defineFrontComponent({
universalIdentifier: '...',
name: 'date-widget',
component: DateWidget,
});
```
### Jak funguje bundlování
Krok sestavení používá esbuild k vytvoření jediného samostatného souboru pro každou logickou funkci a každou frontendovou komponentu. Importované balíčky jsou vloženy přímo do bundlu, s výjimkou těch, které jsou uvedeny níže.
**Logické funkce** běží v prostředí Node.js. Vestavěné moduly Node (`fs`, `path`, `crypto`, `http` atd.) jsou k dispozici a není je třeba instalovat.
**Frontendové komponenty** běží ve Web Workeru. Vestavěné moduly Node nejsou k dispozici — pouze balíčky npm, které fungují v prohlížečovém prostředí. Všimněte si, že sandbox implementuje *částečné* DOM, takže balíček se může bez problémů sestavit, ale přesto selhat za běhu; viz [Aktuální omezení](/l/cs/developers/extend/apps/layout/front-components#current-limitations).
V obou prostředích jsou jako předpřipravené moduly k dispozici `twenty-client-sdk/core` a `twenty-client-sdk/metadata` — nejsou součástí bundlu, ale server je za běhu načítá.
Frontendové komponenty mohou také sdílet závislosti prostřednictvím [balíčku sdílených závislostí](/l/cs/developers/extend/apps/layout/front-components#sharing-dependencies-across-front-components). Balíčky uvedené v poli `frontComponentSharedDependencies` souboru package.json aplikace jsou sestaveny jednou do samostatného souboru a za běhu se načítají z něj, místo aby byly vloženy do každé komponenty. Komponenta, která importuje pouze sdílené balíčky, tak v sobě nese téměř žádný vlastní kód závislostí.
## Nastavení
Vygenerovaná aplikace již obsahuje Vitest. Pokud to nastavujete ručně, nainstalujte závislosti:
```bash filename="Terminal"
yarn add -D vitest vite-tsconfig-paths
```
Vytvořte `vitest.config.ts` v kořeni vaší aplikace:
```ts vitest.config.ts
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '<the pre-seeded local dev key>';
// Make env vars available to globalSetup (test.env only applies to workers)
process.env.TWENTY_API_URL = TWENTY_API_URL;
process.env.TWENTY_API_KEY = TWENTY_API_KEY;
export default defineConfig({
plugins: [
tsconfigPaths({
projects: ['tsconfig.spec.json'],
ignoreConfigErrors: true,
}),
],
test: {
testTimeout: 120_000,
hookTimeout: 120_000,
fileParallelism: false,
include: ['src/**/*.integration-test.ts'],
globalSetup: ['src/__tests__/global-setup.ts'],
env: {
TWENTY_API_URL,
TWENTY_API_KEY,
},
},
});
```
Vytvořte globální soubor pro nastavení, který ověří, že je server dosažitelný, zapíše testovací konfiguraci pro SDK (`~/.twenty/config.test.json`) a před spuštěním testů provede synchronizaci aplikace:
```ts src/__tests__/global-setup.ts
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { appDevOnce, appUninstall } from 'twenty-sdk/cli';
const APP_PATH = process.cwd();
const CONFIG_DIR = path.join(os.homedir(), '.twenty');
export async function setup() {
const apiUrl = process.env.TWENTY_API_URL!;
const apiKey = process.env.TWENTY_API_KEY!;
// Verify the server is running
const response = await fetch(`${apiUrl}/healthz`);
if (!response.ok) {
throw new Error(`Twenty server is not reachable at ${apiUrl}.`);
}
// Write the SDK's test config (the CLI reads config.test.json when NODE_ENV=test)
fs.mkdirSync(CONFIG_DIR, { recursive: true });
fs.writeFileSync(
path.join(CONFIG_DIR, 'config.test.json'),
JSON.stringify({
remotes: { local: { apiUrl, apiKey } },
defaultRemote: 'local',
}, null, 2),
);
// Start from a clean slate, then sync the app
await appUninstall({ appPath: APP_PATH }).catch(() => {});
const result = await appDevOnce({ appPath: APP_PATH });
if (!result.success) {
throw new Error(`Dev sync failed: ${result.error?.message}`);
}
}
export async function teardown() {
await appUninstall({ appPath: APP_PATH });
}
```
## Programová rozhraní SDK
Subcesta `twenty-sdk/cli` exportuje funkce, které můžete volat přímo z testovacího kódu:
| Funkce | Popis |
| -------------- | ------------------------------------------------------------------------------ |
| `appBuild` | Sestaví aplikaci a volitelně zabalí tarball |
| `appDeploy` | Nahraje tarball na server |
| `appDevOnce` | Jednorázově sestaví a synchronizuje aplikaci (stejně jako `yarn twenty apply`) |
| `appInstall` | Nainstaluje aplikaci do aktivního pracovního prostoru |
| `appUninstall` | Odinstaluje aplikaci z aktivního pracovního prostoru |
Každá funkce vrací objekt výsledku se `success: boolean` a buď `data`, nebo `error`.
## Psání integračního testu
Zde je kompletní příklad, který aplikaci sestaví, nasadí a nainstaluje a poté ověří, že se objeví v pracovním prostoru:
```ts src/__tests__/app-install.integration-test.ts
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/application-config';
import { appBuild, appDeploy, appInstall, appUninstall } from 'twenty-sdk/cli';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
const APP_PATH = process.cwd();
describe('App installation', () => {
beforeAll(async () => {
const buildResult = await appBuild({
appPath: APP_PATH,
tarball: true,
onProgress: (message: string) => console.log(`[build] ${message}`),
});
if (!buildResult.success) {
throw new Error(`Build failed: ${buildResult.error?.message}`);
}
const deployResult = await appDeploy({
tarballPath: buildResult.data.tarballPath!,
onProgress: (message: string) => console.log(`[deploy] ${message}`),
});
if (!deployResult.success) {
throw new Error(`Deploy failed: ${deployResult.error?.message}`);
}
const installResult = await appInstall({ appPath: APP_PATH });
if (!installResult.success) {
throw new Error(`Install failed: ${installResult.error?.message}`);
}
});
afterAll(async () => {
await appUninstall({ appPath: APP_PATH });
});
it('should find the installed app in the workspace', async () => {
const metadataClient = new MetadataApiClient();
const result = await metadataClient.query({
findManyApplications: {
id: true,
name: true,
universalIdentifier: true,
},
});
const installedApp = result.findManyApplications.find(
(app: { universalIdentifier: string }) =>
app.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
);
expect(installedApp).toBeDefined();
});
});
```
## Spuštění testů
Ujistěte se, že běží váš lokální server Twenty, a poté:
```bash filename="Terminal"
yarn test
```
Nebo v režimu watch během vývoje:
```bash filename="Terminal"
yarn test:watch
```
## Kontrola typů
Kontrolu typů můžete spustit i na vaší aplikaci bez spuštění testů:
```bash filename="Terminal"
yarn twenty dev:typecheck
```
Spustí se `tsc --noEmit` proti souboru `tsconfig.json` vaší aplikace a nahlásí se případné chyby typů. Vygenerované aplikace také obsahují skript `yarn typecheck`, který pokrývá i testovací soubory (`tsconfig.spec.json`).
## CI s GitHub Actions
Generátor kostry vytvoří připravený k použití workflow v `.github/workflows/ci.yml`. Při každém pushi do `main` a při každém pull requestu spustí efemérní server Twenty v runneru (pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test`), poté spustí `yarn lint`, `yarn typecheck`, `yarn test:unit` a `yarn test` s `TWENTY_API_URL` / `TWENTY_API_KEY` směřujícími na tento server. Nejsou vyžadovány žádné tajné údaje a verzi serveru můžete připnout pomocí proměnné prostředí `TWENTY_VERSION` na začátku workflow.
Úplný postup pro tři workflow generované kostrou (`ci.yml`, nasazovací pipeline `cd.yml` a `publish.yml` pro publikování na npm) najdete v části [Publishing → Automated CI/CD](/l/cs/developers/extend/apps/operations/publishing#automated-cicd-scaffolded-workflows).

View File

@@ -0,0 +1,136 @@
---
title: Překlady
description: Doručte svou aplikaci v několika jazycích — překládejte popisky manifestu a texty uživatelského rozhraní prostřednictvím jednoho katalogu locales.
icon: language
---
Aplikace Twenty jsou psané v **angličtině**: řetězce ve vašem zdrojovém kódu a manifestu tvoří anglický zdrojový text, `en` je zdrojová locale, ze které překládáte, a jakákoli locale ponechaná bez překladu se na ni automaticky vrátí.
Vaše aplikace má dva druhy přeložitelného textu a oba procházejí stejným
katalogem `locales/`:
* **Popisky manifestu** — názvy objektů a polí, názvy zobrazení, položky nabídky a
další řetězce deklarované v metadatech vaší aplikace.
* **Řetězce front komponent** — text uživatelského rozhraní, který vykreslují vaše React front komponenty.
Označíte přeložitelné řetězce, extrahujete je do katalogů pro jednotlivé jazyky,
přeložíte tyto katalogy a build doručí správný jazyk pro
aktuálního uživatele — bez dalšího zapojování.
## Označování řetězců front komponent
Importujte pomocné překladové funkce z `twenty-sdk/front-component`:
```tsx
import { Trans, t, msg, useTranslate } from 'twenty-sdk/front-component';
const STATUSES = [
{ id: 'draft', label: msg('Draft') },
{ id: 'sent', label: msg('Sent') },
];
const Card = ({ count, name }: { count: number; name: string }) => {
const { t } = useTranslate();
return (
<section>
{/* Static text — reactive to the user's locale */}
<Trans>Loading postcard…</Trans>
{/* Disambiguate identical sources with a context */}
<Trans context="card-title">Untitled</Trans>
{/* Interpolation: pass values explicitly */}
<p>{t('Hi {name}', { name })}</p>
<p>{t('Saved {count} cards', { count })}</p>
{/* Resolve a lazily-declared descriptor */}
<ul>{STATUSES.map((s) => <li key={s.id}>{t(s.label)}</li>)}</ul>
</section>
);
};
```
### Kdy co použít
* **`Trans`** — statický text v JSX. Použijte props `message` a `values` pro
interpolaci, jak je ukázáno v `Trans message="Hi {name}" values={{ name }}`;
interpolace přímo v potomcích není staticky extrahovatelná.
* **`useTranslate().t`** — dynamické řetězce uvnitř komponenty. Znovu se vykreslí, když
uživatel přepne jazyk. Uvnitř renderu tomuto dávejte přednost.
* **`t(...)`** (importované přímo) — okamžitý překlad použitelný **kdekoli**,
včetně obsluh událostí, pomocných funkcí a v modulovém rozsahu — nejen uvnitř renderu.
* **`msg(...)`** — líný deskriptor pro řetězce deklarované jako data (konstanty,
konfigurace). Vyhodnoťte jej později pomocí `t(descriptor)`.
### Kontext
Předejte `context`, abyste odlišili identické zdrojové řetězce, které se překládají
rozdílně:
```tsx
t({ message: 'Open', context: 'door' });
t({ message: 'Open', context: 'window' });
<Trans context="card-title">Untitled</Trans>
```
Kontextové řetězce se v jazykových katalozích objevují jako skupina s klíčem podle svého kontextu: `"\<context>": { "\<message>": "\<translation>" }`. Štítky manifestu jsou kontextové již svou konstrukcí — každý z nich nese roli metadat, kterou plní (`objectMetadata.labelSingular`, `fieldMetadata.label`, `view.name`, …), takže stejné slovo se může přeložit odlišně jako název objektu a jako název zobrazení.
## Extrahování a překládání
Spusťte příkaz pro extrakci z adresáře své aplikace:
```bash
twenty dev:translations-extract # collect strings into locales/en.json
twenty dev:translations-extract --locale fr-FR # also scaffold a target locale
```
Extrakce shromáždí jak popisky manifestu, tak řetězce `t()`/`msg()`/`Trans`
z vašeho zdrojového kódu front komponent do `locales/\<locale>.json`, kde jsou klíčem
zdrojové řetězce. Doplňte překlady:
```json
// locales/fr-FR.json
{
"Loading postcard…": "Chargement de la carte…",
"Hi {name}": "Bonjour {name}",
"Saved {count} cards": "{count} cartes enregistrées",
"door": {
"Open": "Ouvrir"
},
"objectMetadata.labelSingular": {
"Postcard": "Carte postale"
}
}
```
Řetězce bez kontextu jsou ploché položky; kontextové řetězce — včetně každého štítku manifestu — jsou vnořené pod svým kontextovým klíčem.
Zástupné symboly jako `{name}` jsou nahrazeny za běhu — ponechejte je v
překladu. Jakýkoli řetězec ponechaný prázdný spadne zpět na zdrojový text.
## Jak to běží
`twenty dev:build` zkompiluje katalogy a doručí správný jazyk pro
aktuálního uživatele: popisky manifestu jsou vyhodnoceny na straně serveru a katalogy
front komponent jsou zabudovány do každého balíčku komponent. Za běhu komponenta čte
jazykovou verzi (locale) z vykonávacího kontextu (aktuální jazyk hostitele) a překládá
každý řetězec podle svého katalogu, přičemž při chybějícím překladu spadne zpět na zdroj. Přepnutí jazyka v hostiteli znovu vykreslí řetězce `Trans` a
`useTranslate().t` v reálném čase.
Popisky manifestu se kompilují při každé synchronizaci — stejně při `twenty dev:build`, `twenty apply`
jako při průběžném sledování pomocí `twenty dev` — takže pro zobrazení nového textu stačí upravit soubor lokalizace a
provést synchronizaci. Katalogy komponent Front se při sestavení začleňují do
každého balíčku komponent, takže změna některého z nich stále znamená
znovu spustit `twenty dev:build` (a znovu nasadit).
Textoví potomci `Trans` mohou být na více řádcích — bílé znaky se slučují
stejně jako je slučuje JSX, takže oba tyto případy se extrahují pod klíčem `Welcome back`:
```tsx
<Trans>Welcome back</Trans>
<Trans>
Welcome
back
</Trans>
```

View File

@@ -0,0 +1,81 @@
---
title: 5. An AI agent
icon: robot
description: Nechte agenta generovat dokumenty z chatu, pomocí vašeho nástroje.
---
Protože je `generate-document` vystaven jako **nástroj**, agent AI ho může zavolat.
Přidejme agenta a dovednosti, aby uživatelé mohli říci *"vygenerovat návrh
Jeffery Griffin"*.
## Dovednost
[skill](/l/cs/developers/extend/apps/logic/skills-and-agents) je opakovaně použitelné
– znalosti, které připojujete k agentům. Náš model učí jak použít
nástroj.
```ts filename="src/skills/document-drafting.skill.ts"
import { defineSkill } from 'twenty-sdk/define';
export default defineSkill({
universalIdentifier: DOCUMENT_SKILL_UNIVERSAL_IDENTIFIER,
name: 'document-drafting',
label: 'Document drafting',
icon: 'IconFileText',
content: [
'To generate a document, call the `generate-document` tool with:',
'- `templateId`: the id of the document template to use.',
'- `recordId`: the id of the Person or Company the document is for.',
'',
'If the user names a template or person instead of an id, find the record first,',
'then pass its id. Make sure the template target matches the record type.',
].join('\n'),
});
```
## Zástupce
[agent](/l/cs/developers/extend/apps/logic/skills-and-agents) spáruje výzvu s modelem
. Nastavte `responseFormat` explicitně, abyste se vyhnuli varování sestavení.
```ts filename="src/agents/document-assistant.agent.ts"
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: DOCUMENT_AGENT_UNIVERSAL_IDENTIFIER,
name: 'document-assistant',
label: 'Document Assistant',
description: 'Generates documents from your templates and CRM records.',
icon: 'IconFileText',
responseFormat: { type: 'text' },
prompt: [
'You are the Document Assistant for a CRM.',
'You help users generate personalized documents from reusable templates',
'and the data already in their CRM. Use the generate-document tool, and',
'always confirm what you created.',
].join(' '),
});
```
<Note>
Zástupce může zavolat nástroj pouze v případě, že jej jeho role umožňuje. Již jsme nastavili
`canAccessAllTools: true` a `canBeAssignedToAgents: true` na roli aplikace v
[Chapter 2](/l/cs/developers/extend/apps/tutorials/document-generator/generating-documents#grant-it-access).
</Note>
## Vyzkoušejte
Otevřete chat s **asistentem dokumentu** a požádejte jej, aby připravil dokument pro osobu
ve vašem CRM. Najde záznam, volání `generate-document`, a nahlásí
dokument, který vytvořil – který se nyní zobrazí ve tvém **dokumentu** zobrazení,
přesně se líbí příkazové menu a cesty workflow.
To je výsledek odhalování logiky jako nástroje: **jedna funkce, mnoho předních dveří** —
příkaz menu, HTTP, krok workflow a nyní přirozený jazyk.
**Po tomto kroku:** je aplikace kompletní a opravdu užitečná. Čas do
lodi.
<Card title="Další: publikování →" icon="rocket" href="/l/cs/developers/extend/apps/tutorials/document-generator/publishing">
Přidat metadata a publikovat tržiště.
</Card>

View File

@@ -0,0 +1,308 @@
---
title: 4. Budování uživatelského rozhraní
icon: table-columns
description: Zobrazení, postranní navigace, příkaz a přední komponenty.
---
Právě teď jsou objekty dosažitelné pouze v nastavení. Dejme aplikaci
skutečnou přítomnost v UI: seznam zobrazení, položek postranního panelu, jedním kliknutím
**Generovat příkaz** dokumentu, přední část rekordové stránky do **náhledu** dokumentu
a nativní text **editor** pro šablony.
## Zobrazení a navigace
[view](/l/cs/developers/extend/apps/layout/views) je uložený seznam daného objektu.
[Položka navigačního menu](/l/cs/developers/extend/apps/layout/navigation-menu-items)
umístí toto zobrazení do postranního panelu. Server už pro každý objekt automaticky poskytuje výchozí zobrazení „All ...“, takže zobrazení, která aplikace definuje, jsou další, pečlivě vybraná.
```ts filename="src/views/documents.view.ts"
import { defineView } from 'twenty-sdk/define';
export default defineView({
universalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
name: 'All documents',
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconFile',
position: 0,
fields: [
{ universalIdentifier: DOCUMENTS_VIEW_NAME_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0, isVisible: true, size: 280 },
{ universalIdentifier: DOCUMENTS_VIEW_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: DOCUMENT_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
position: 1, isVisible: true, size: 120 },
{ universalIdentifier: DOCUMENTS_VIEW_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
fieldMetadataUniversalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
position: 2, isVisible: true, size: 200 },
],
});
```
```ts filename="src/navigation-menu-items/documents.navigation-menu-item.ts"
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier: DOCUMENTS_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
name: 'Documents',
icon: 'IconFile',
color: 'green',
position: 1,
type: NavigationMenuItemType.VIEW,
viewUniversalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
});
```
Přidejte stejný pár šablon. Oba se nyní zobrazí v postranním panelu:
<Frame caption="Dokumenty a šablony v postranním panelu s vygenerovaným dokumentem.">
<img src="/images/docs/developers/extends/apps/document-generator/04-documents-view.png" alt="Zobrazení dokumentů s vygenerovaným dokumentem" />
</Frame>
## Přední část
[přední komponenta](/l/cs/developers/extend/apps/layout/front-components) je komponenta React
boxovaná uvnitř dvaceti. Náš přečte vybraný záznam, načte
šablony osob přes `CoreApiClient`, a POSTE na trasu z poslední kapitoly
.
```tsx filename="src/front-components/generate-document-form.front-component.tsx"
import { useEffect, useState } from 'react';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component';
const GenerateDocumentForm = () => {
const selectedRecordIds = useSelectedRecordIds();
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
const [templates, setTemplates] = useState<{ id: string; name: string }[]>([]);
const [templateId, setTemplateId] = useState('');
useEffect(() => {
new CoreApiClient()
.query({ documentTemplates: {
__args: { filter: { target: { eq: 'PERSON' } }, first: 100 },
edges: { node: { id: true, name: true } } } })
.then(({ documentTemplates }) => {
const list = documentTemplates?.edges?.map((e) => e.node) ?? [];
setTemplates(list);
if (list[0]) setTemplateId(list[0].id);
});
}, []);
const generate = async () => {
const res = await new RestApiClient().post<{ success: boolean }>(
'/s/documents/generate',
{ templateId, recordId },
);
await enqueueSnackbar({
message: res.success ? 'Document generated.' : 'Generation failed.',
variant: res.success ? 'success' : 'error',
});
};
// ...render a <select> of templates and a Generate button
};
export default defineFrontComponent({
universalIdentifier: GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
name: 'generate-document-form',
component: GenerateDocumentForm,
});
```
<Warning>
Styl s inline CSS proměnnými (`var(--t-color-blue)`), ne hodnoty importované z
`twenty-ui`. SDK mocniny, které balí během sestavení, takže importy
téma na úrovni modulů by byly `nedefinované`. Podívejte se na
[plnou komponentu](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/front-components/generate-document-form.front-component.tsx).
</Warning>
## Příkaz k otevření
[položka příkazu](/l/cs/developers/extend/apps/layout/command-menu-items) s
`availabilityType: 'RECORD_SELECTION'` se zobrazí při výběru osoby, a
otevře komponentu v postranním panelu.
```ts filename="src/command-menu-items/generate-document.command-menu-item.ts"
import { defineCommandMenuItem, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
export default defineCommandMenuItem({
universalIdentifier: GENERATE_DOCUMENT_COMMAND_UNIVERSAL_IDENTIFIER,
label: 'Generate document',
availabilityType: 'RECORD_SELECTION',
availabilityObjectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
frontComponentUniversalIdentifier:
GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
});
```
## Vyzkoušejte celý tok
Otevřete **People**, zaškrtněte osobu a stiskněte <kbd>⌘K</kbd> / <kbd>Ctrl K</kbd>.
"Generovat doklad" se zobrazí, označeno vaší aplikací:
<Frame caption="Příkaz se zobrazí při výběru osoby.">
<img src="/images/docs/developers/extends/apps/document-generator/06-command-menu.png" alt="Příkazové menu s generováním dokumentu" />
</Frame>
Spusťte - komponenta se otevře v postranním panelu. Vyberte si šablonu, klikněte na
**Generovat**, a nový záznam země v **dokumentech**.
<Frame caption="Přední komponenta, načítání šablon a generování kliknutím.">
<img src="/images/docs/developers/extends/apps/document-generator/06b-front-component.png" alt="Generovat boční panel dokumentu" />
</Frame>
Každý vygenerovaný dokument zaznamenává vaši aplikaci jako autora:
<Frame caption="Vytvořeno generátorem dokumentů, stav vygenerován.">
<img src="/images/docs/developers/extends/apps/document-generator/05-document-record.png" alt="Generovaný záznam dokladu" />
</Frame>
## Náhled dokumentu na jeho stránce s záznamem
Přední komponenta není pouze pro menu příkazů — jeden můžete připojit jako \*\*kartu na stránce
záznamu \*\*. Přidejme kartu \*Náhled \* k záznamu dokumentu, která vykreslí
Markdown tělo jako leštěnou, vytisknutelnou stránku.
Komponenta čte aktuální id záznamu z kontextového kontextu, načte
dokument a vykreslí jej. Přední komponenty běží v **sandboxu**, který povoluje pouze
bílou listinu HTML tagů — syrovou injekci HTML (`dangerouslySetInnerHTML`) a
`\<style>` jsou blokovány — takže Markdown vykreslujeme jako React prvky s inline
pomocí malého [`Markdown`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/markdown-to-react.tsx)
helper.
```tsx filename="src/front-components/document-viewer.front-component.tsx"
import { CoreApiClient } from 'twenty-client-sdk/core';
import { RestApiClient } from 'twenty-client-sdk/rest';
import { defineFrontComponent } from 'twenty-sdk/define';
import { useFrontComponentExecutionContext } from 'twenty-sdk/front-component';
import { Markdown } from 'src/utils/markdown-to-react';
const DocumentViewer = () => {
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
// ...load { content, file } for recordId, then derive the links:
const pdfUrl = document.file?.[0]?.url;
const webUrl = new RestApiClient().resolveUrl('/s/documents/view', {
query: { id: recordId },
});
// Render the template body, plus quick links to the web page and the PDF.
// Links open in a new tab so they don't navigate the embedded component.
return (
<div style={styles.scroll}>
<div style={styles.actions}>
<a style={styles.actionLink} href={webUrl} target="_blank" rel="noopener noreferrer">
Open web page
</a>
{pdfUrl ? (
<a style={styles.actionLink} href={pdfUrl} target="_blank" rel="noopener noreferrer">
Download PDF
</a>
) : null}
</div>
<div style={styles.paper}>
<div style={styles.body}>
<Markdown content={document.content} />
</div>
</div>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
name: 'document-viewer',
component: DocumentViewer,
});
```
Připojit ji s [page layout](/l/cs/developers/extend/apps/layout/page-layouts). Rozvržení
`RECORD_PAGE` přidá k zobrazení záznamu objektu karty; karta s jediným
widgetem `FRONT_COMPONENT` hostí komponentu přes celou šířku:
```ts filename="src/page-layouts/document-record.page-layout.ts"
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
export default definePageLayout({
universalIdentifier: DOCUMENT_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
name: 'Document record page',
type: 'RECORD_PAGE',
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
tabs: [{
universalIdentifier: DOCUMENT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
title: 'Preview',
icon: 'IconEye',
position: 50,
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
widgets: [{
universalIdentifier: DOCUMENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
title: 'Document preview',
type: 'FRONT_COMPONENT',
configuration: {
configurationType: 'FRONT_COMPONENT',
frontComponentUniversalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
},
}],
}],
});
```
Otevřete jakýkoliv dokument - záložka \*\*Náhled \*\* jej krásně vykreslí s odkazy na
sdílitelnou webovou stránku a PDF:
<Frame caption="Karta Náhled vykresluje dokument s vloženými styly, plus rychlé odkazy.">
<img src="/images/docs/developers/extends/apps/document-generator/09-document-viewer.png" alt="Hlavní komponenta prohlížeče dokumentů na záložce záznamu" />
</Frame>
## Upravit šablonu pomocí editoru s bohatým textem
Šablony vůbec nepotřebují vlastní komponentu. Protože je `body`
`RICH_TEXT`, Dvacet již poskytuje plně bohatý textový editor –
stejné jako standardní použití objektů poznámky a úkolu. Prostě jsme ji vykreslili na
šablonové stránce záznamu.
Přidejte kartu s `FIELD` widgetem v režimu zobrazení `EDITOR` ukazujícím na pole `body`
pomocí `fieldMetadataId`:
```ts filename="src/page-layouts/template-record.page-layout.ts"
{
universalIdentifier: TEMPLATE_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
title: 'Template',
position: 1,
layoutMode: PageLayoutTabLayoutMode.GRID,
widgets: [{
universalIdentifier: TEMPLATE_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
title: 'Template',
type: 'FIELD',
position: { layoutMode: PageLayoutTabLayoutMode.GRID, row: 0, column: 0, rowSpan: 6, columnSpan: 12 },
configuration: {
configurationType: 'FIELD',
fieldMetadataId: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
fieldDisplayMode: 'EDITOR',
},
}],
}
```
Pole `RICH_TEXT` ukládá JSON blok editoru a Markdown
projekci. Plynovod pro výrobu čte tyto Markdown projekce, takže
zástupné symboly, PDF, a sdílitelná webová stránka všechny nefungují beze změny —
viz celý
[`template-record. age-layout.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/page-layouts/template-record.page-layout.ts).
Widgety `FIELD` podporují také další režimy zobrazení a možnosti, včetně
výpisu záznamů vnořené relace vzdálené o dva kroky — viz
[referenční dokumentaci k widgetu Field](/l/cs/developers/extend/apps/layout/page-layouts#field-widgets).
Nyní editoři píší šablony v řádném editoru s bohatým textem:
<Frame caption="Šablona: Dvacátý nativní textový editor vázaný na pole těla.">
<img src="/images/docs/developers/extends/apps/document-generator/10-template-editor.png" alt="Záznam šablony s nativním textovým editorem" />
</Frame>
**Po tomto kroku:** náhled dokumentů krásně a šablony jsou editovatelné
v aplikaci. Dále nechte AI agenta generovat je z chatu.
<Card title="Další: agent AI →" icon="robot" href="/l/cs/developers/extend/apps/tutorials/document-generator/ai-agent">
Přidejte agenta a dovednosti, které volají po nástroji.
</Card>

View File

@@ -0,0 +1,135 @@
---
title: 1. Datový model
icon: database
description: Vzor dokumentů a šablon s objekty, polími a vztahem.
---
Naše aplikace potřebuje dva vlastní objekty: **šablony dokumentů** (co psát) a
**dokumenty** (generovaný výsledek). Pojďme je definovat.
Scaffold each entity file with the CLI — has a valid UUID and the right
folder for you:
```bash filename="Terminal"
yarn twenty dev:add object
```
Níže zobrazujeme dokončené soubory.
<Note>
Každý `*_UNIVERSAL_IDENTIFIER` trvale žije v
`src/constants/universal-identifiers.ts` a je importován, pokud je použit. snippety
níže vynechávají tyto importy pro brevitu - uchovejte je ve svých vlastních souborech.
</Note>
## Objekt šablony
Šablona má `name`, `body` s `{{placeholders}}`, a `cíl`, který
říká, zda je napsán pro osobu nebo společnost. `Těloy` je
`RICH_TEXT` pole, takže dvacet jí dává plně bohatý textový editor.
```ts filename="src/objects/document-template.object.ts"
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
universalIdentifier: DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER,
nameSingular: 'documentTemplate',
namePlural: 'documentTemplates',
labelSingular: 'Document template',
labelPlural: 'Document templates',
icon: 'IconFileText',
labelIdentifierFieldMetadataUniversalIdentifier:
TEMPLATE_NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{ universalIdentifier: TEMPLATE_NAME_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT, name: 'name', label: 'Name', icon: 'IconAbc' },
{ universalIdentifier: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.RICH_TEXT, name: 'body', label: 'Body', icon: 'IconFileText',
description: 'Use {{placeholders}} like {{name.firstName}} or {{jobTitle}}.' },
{ universalIdentifier: TEMPLATE_TARGET_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.SELECT, name: 'target', label: 'Target', icon: 'IconTarget',
defaultValue: `'PERSON'`,
options: [
{ id: TEMPLATE_TARGET_OPTION_PERSON_UNIVERSAL_IDENTIFIER,
value: 'PERSON', label: 'Person', color: 'blue', position: 0 },
{ id: TEMPLATE_TARGET_OPTION_COMPANY_UNIVERSAL_IDENTIFIER,
value: 'COMPANY', label: 'Company', color: 'green', position: 1 },
] },
],
});
```
<Warning>
`SELECT` volba **hodnoty** musí být `UPPER_CASE` (`PERSON`, ne `person`) a
`defaultValue` je zabalena do extra uvozovek: `` `'PERSON'` ``. `Štítek` je to, co vidí
uživatelé.
</Warning>
## Objekt dokumentu
Vygenerovaný dokument ukládá vykreslený `content` a `status`. Definujte
stejným způsobem s `status` výběrem `DRAFT` / `GENERATED`. Celý soubor:
[`document.object.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/objects/document.object.ts).
## Propojení se vztahem
Každý dokument by se měl vrátit ke šabloně, odkud pochází. Vztahy jsou
**obousměrné** — definujete obě strany, každý ve vlastním souboru polí.
```ts filename="src/fields/document-template-relation.field.ts"
import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk/define';
// The "many" side: each document belongs to one template.
export default defineField({
universalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'template',
label: 'Template',
relationTargetObjectMetadataUniversalIdentifier:
DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier:
TEMPLATE_DOCUMENTS_FIELD_UNIVERSAL_IDENTIFIER,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'templateId',
},
});
```
Druhá strana (`template-documents-relation.field.ts`) je
`RelationType.ONE_TO_MANY` pole `documents`, které ukazuje opačnou cestu.
Celý vzor viz [Relations](/l/cs/developers/extend/apps/data/relations).
## Podívejte se na to ve dvaceti letech
S spuštěním `příze dvacet dev` otevřete **Nastavení → Datový model**. Oba objekty
se zobrazí a označují vaší aplikací.
<Frame caption="Oba vlastní objekty v aplikaci Generátor dokumentů.">
<img src="/images/docs/developers/extends/apps/document-generator/01-data-model.png" alt="Nastavení datového modelu zobrazující dokumenty a šablony dokumentů" />
</Frame>
Vytvořte jednu šablonu pro testování - pojmenujte ji *návrhem prodeje*, nastavte **Cíl** na
*Osobní* a vložte tělo s několika zástupnými znaky:
```text
Dear {{name.firstName}} {{name.lastName}},
As {{jobTitle}} at {{company.name}}, we think you'll love our product.
Best,
The Team
```
<Frame caption="Záznam šablony. Subjekt uchovává své zástupné znaky, dokud není doklad vygenerován.">
<img src="/images/docs/developers/extends/apps/document-generator/03-template-record.png" alt="Záznam šablony obchodního návrhu s zástupným tělem" />
</Frame>
**Po tomto kroku:** máte `documentTemplate` a `document` objekty, propojené
relací a jednu šablonu, ze které chcete vygenerovat. Dále platí, že logika, která ji naplňuje.
<Card title="Další: generování dokumentů →" icon="bolt" href="/l/cs/developers/extend/apps/tutorials/document-generator/generating-documents">
Napište logickou funkci, která vyplňuje šablonu.
</Card>

View File

@@ -0,0 +1,239 @@
---
title: 2. Generování dokumentů
icon: bolt
description: Jedna logická funkce, zobrazená jako nástroj AI a akce pracovního postupu.
---
Nyní jádro: [logická funkce](/l/cs/developers/extend/apps/logic/logic-functions)
, která načte šablonu a záznam, vyplní zástupné symboly a uloží nový
dokument.
Obchodní logiku napíšeme jednou jako **handler**, pak ji vystavíme pomocí
několika spouštěčů. Tato kapitola vrací dva z nich – **nástroj AI** a
**akce pracovního postupu**.
## Pomocník pro vykreslování
Uchovávejte čistou logiku ve svém vlastním souboru, takže je snadné testovat jednotku. Toto zarovná záznam
do `{{dot.path}}` tokenů a nahradí je.
```ts filename="src/logic-functions/utils/render-template.ts"
const PLACEHOLDER_PATTERN = /\{\{\s*([\w.]+)\s*\}\}/g;
export const renderTemplate = (body: string, values: Record<string, string>) => {
const missingTokens = new Set<string>();
const content = body.replace(PLACEHOLDER_PATTERN, (_m, token: string) => {
const value = values[token];
if (value === undefined || value === '') { missingTokens.add(token); return ''; }
return value;
});
return { content, missingTokens: [...missingTokens] };
};
```
<Tip>
Protože tento soubor nemá žádné vedlejší účinky, můžete jej zakrýt rychlými jednotkovými testy
(`yarn test:unit`). Viz [Testing](/l/cs/developers/extend/apps/operations/testing).
</Tip>
## Řidič
Pracovník používá vygenerovaná [`CoreApiClient`](/l/cs/developers/extend/apps/logic/logic-functions)
pro čtení a zápis dat CRM. Načte šablonu, načte cílový záznam, vyplní
tělo a vytvoří `dokument`.
```ts filename="src/logic-functions/handlers/generate-document-handler.ts"
import { CoreApiClient } from 'twenty-client-sdk/core';
import { loadRecordValues } from 'src/logic-functions/utils/load-record-values';
import { renderTemplate } from 'src/logic-functions/utils/render-template';
export const generateDocumentHandler = async (
input: { templateId: string; recordId: string },
) => {
const client = new CoreApiClient();
// Use a filtered list query, not the singular lookup: the singular query
// throws when nothing matches, which would become a 500 instead of a 404.
const { documentTemplates } = await client.query({
documentTemplates: {
__args: { filter: { id: { eq: input.templateId } }, first: 1 },
edges: { node: { id: true, name: true, body: true, target: true } },
},
});
const documentTemplate = documentTemplates?.edges?.[0]?.node;
if (!documentTemplate?.id) return { success: false, status: 404, message: 'Template not found.' };
const record = await loadRecordValues(client, documentTemplate.target, input.recordId);
if (!record.found) return { success: false, status: 404, message: 'Record not found.' };
const { content, missingTokens } = renderTemplate(documentTemplate.body ?? '', record.values);
const { createDocument } = await client.mutation({
createDocument: {
__args: { data: {
name: `${documentTemplate.name} — ${record.displayName}`,
content, status: 'GENERATED', templateId: documentTemplate.id,
} },
id: true, name: true,
},
});
return { success: true, documentId: createDocument.id, content, missingTokens };
};
```
`loadRecordValues` spouští jiný dotaz na Osobu vs. společnost a flattens
výsledek — viz
[`load-record-values.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/load-record-values.ts).
## Vystavit ji jako nástroj a akce pracovního postupu
Jediný `defineLogicFunction` může obsahovat několik spouštěčů. Zde `toolTriggerSettings`
dělá volatelné AI agenty a `workflowActionTriggerSettings` jej promění v
krok na vizuálním workflow. Obě popisují svůj vstup se schématem JSON.
```ts filename="src/logic-functions/generate-document.ts"
import { defineLogicFunction } from 'twenty-sdk/define';
import { jsonSchemaToInputSchema } from 'twenty-sdk/logic-function';
import { GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
import { generateDocumentInputSchema } from 'src/logic-functions/schemas/generate-document-input.schema';
export default defineLogicFunction({
universalIdentifier: GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
name: 'generate-document',
description: 'Generate a document from a template and a CRM record.',
timeoutSeconds: 30,
toolTriggerSettings: {
inputSchema: generateDocumentInputSchema,
},
workflowActionTriggerSettings: {
label: 'Generate Document',
icon: 'IconFileText',
inputSchema: jsonSchemaToInputSchema(generateDocumentInputSchema),
outputSchema: [{ type: 'object', properties: {
success: { type: 'boolean' }, documentId: { type: 'string' },
} }],
},
handler: generateDocumentHandler,
});
```
Schéma vstupů je schéma prostého JSON popisující `templateId` a `recordId` —
viz [`generate-document-input.schema.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/schemas/generate-document-input.schema.ts).
## Povolit přístup
Logické funkce běží jako role aplikace. Potřebuje přečíst šablony a záznamy
a vytvořit dokumenty, aby v `src/roles/default-role.ts`:
```ts
export default defineApplicationRole({
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
label: 'Document Generator default role',
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canAccessAllTools: true,
canBeAssignedToAgents: true,
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.UPLOAD_FILE],
});
```
`UPLOAD_FILE` umožňuje funkci nahrát vygenerovaný PDF v další sekci.
Pro jemnozrnná oprávnění viz [Roles](/l/cs/developers/extend/apps/config/roles).
## Připojit skutečný soubor PDF
Vykreslené textové pole je užitečné, ale uživatelé chtějí skutečný dokument. Vygenerujme
**PDF** a uložme jej do záznamu jako soubor ke stažení.
Nejprve zadejte objekt `document` pole `FILES` pro držení PDF. Aplikace nahrávají
do svých **vlastních** polí souborů, takže toto pole slouží jako trasa nahrávání:
```ts filename="src/objects/document.object.ts"
{
universalIdentifier: DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.FILES,
name: 'file',
label: 'File',
icon: 'IconFileTypePdf',
universalSettings: { maxNumberOfValues: 1 },
}
```
Nyní vytvořte tento PDF. Aplikace je skutečný projekt uzlu, takže můžete přidat libovolný balíček npm
, který potřebujete a importovat jako kdekoli jinde. Používáme **[pdf-lib](https://pdf-lib.js.org/)**
k nakreslení PDF a **[marked](https://marked.js.org/)** k rozepsání těla Markdown
– CLI je nainstaluje do běhu funkce pro vás:
```bash filename="Terminal"
yarn add pdf-lib marked
```
Úplný pomocník je
[`generate-document-pdf.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/generate-document-pdf.ts).
Parazuje Markdown do tokenů s `označeným. exer`, pak je stanoví s
pdf-lib: skutečné nadpisy, **tučné**/*italic* běhy, odrážky a číslované seznamy,
blokuje kotace a pravidla – leštěné, vícestránkové A4 vykreslování šablony
samotné, nikoli zeď textu.
<Frame caption="Vygenerovaný PDF: skutečné typografie a Markdown formátování, vykreslování těla šablony.">
<img src="/images/docs/developers/extends/apps/document-generator/07b-generated-pdf.png" alt="Leštěný, tržně generovaný PDF" />
</Frame>
<Note>
Vestavěné fonty ve formátu pdf-lib, používají WinAnsi kódování, takže západoevropské akcenty vyřadí
z krabice; pomocník mapy chytrých uvozovek a pomlček a kapne znaky, které nemůže kódovat
. Vykreslování nelatinských skriptů (čínské, arabské, cyrilice) by znamenalo, že by
vložil písmo Unicode.
</Note>
Pak ho nahrajte a uložte odkaz do záznamu. `uploadFile` routy bytů
do vašeho pole souborů vlastněných aplikací; vrácený `id` je to, co uložíte:
```ts filename="src/logic-functions/handlers/generate-document-handler.ts"
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { generateDocumentPdf } from 'src/logic-functions/utils/generate-document-pdf';
const documentName = `${documentTemplate.name} — ${record.displayName}`;
const bytes = await generateDocumentPdf(documentName, content);
const fileName = 'proposal.pdf';
const uploaded = await new MetadataApiClient().uploadFile(
Buffer.from(bytes),
fileName,
'application/pdf',
DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
);
await client.mutation({
updateDocument: {
__args: {
id: documentId,
data: { file: [{ fileId: uploaded.id, label: fileName }] },
},
id: true,
},
});
```
Vygenerovaný dokument má nyní stažitelný PDF:
<Frame caption="Vygenerovaný PDF uložený v poli Soubor dokumentu.">
<img src="/images/docs/developers/extends/apps/document-generator/08-document-with-pdf.png" alt="Záznam dokladu s vygenerovaným souborem PDF" />
</Frame>
<Note>
`uploadFile` se zaměřuje pouze na **soubory vlastněné aplikací** (takže nahrávání vždy vyžaduje aplikaci
, která toto pole vlastní, plus proměnnou role `UPLOAD_FILE`). To je důvod, proč PDF
přistane na vlastním `file` pole záznamu — stejný vzor
[call-recorder app](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/public/call-recorder)
používá pro nahrávání.
</Note>
**Po tomto kroku:** každý vygenerovaný dokument má skutečný, stahovatelný PDF. Ale
nemůže *volat* generátor z uživatelského rozhraní - pro to potřebujeme HTTP trasu.
<Card title="Další: HTTP trasy →" icon="zeměkoule" href="/l/cs/developers/extend/apps/tutorials/document-generator/http-routes">
Zapněte funkci přes HTTP a vykreslete dokumenty jako webové stránky.
</Card>

View File

@@ -0,0 +1,150 @@
---
title: 3. HTTP trasy
icon: globe
description: Spuštění funkce přes HTTP a vykreslení dokumentů jako webových stránek.
---
Stejný handler může také odpovědět na HTTP požadavky. Přidáme dva trasy:
* **POST** koncový bod uživatelského rozhraní volá, aby vytvořilo dokument, a
* veřejný **GET** koncový bod, který vykresluje dokument jako tiskovou webovou stránku.
Oba použijte `httpRouteTriggerSettings`. Trasy aplikace jsou dostupné pomocí cesty `/s/`; na lokálních
a self-hostovaných instancích tuto cestu obsluhuje váš server Twenty
(např. `http://localhost:2020/s/documents/generate`). Ve Twenty Cloudu `RestApiClient`
sestavuje cestu prostřednictvím domény funkcí pracovního prostoru.
## POST trasa – generovat na požádání
Toto znovu používá `generateDocumentHandler`, takže není logika opakovat - jen tenký
adaptér, který čte tělo požadavku.
```ts filename="src/logic-functions/generate-document-route.ts"
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
import { Response } from 'twenty-sdk/logic-function';
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
const handler = async (event: RoutePayload): Promise<Response> => {
const body = event.body as Record<string, unknown> | null;
const result = await generateDocumentHandler({
templateId: (body?.templateId as string) ?? '',
recordId: (body?.recordId as string) ?? '',
});
// Map the handler's failure reason onto a real HTTP status (400/404/500)
// instead of always returning 200.
return new Response(JSON.stringify(result), {
status: result.success ? 200 : (result.status ?? 400),
headers: { 'Content-Type': 'application/json' },
});
};
export default defineLogicFunction({
universalIdentifier: GENERATE_DOCUMENT_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'generate-document-route',
timeoutSeconds: 30,
handler,
httpRouteTriggerSettings: {
path: '/documents/generate',
httpMethod: 'POST',
isAuthRequired: true,
},
});
```
Sdílený handler vrátí navržený `status` při selhání, takže trasa může
odpovědět správným `4xx`/`5xx` kódem. `isAuthRequd: true` znamená, že volající
musí prezentovat platný token — přední komponenta v další kapitole automaticky prochází přístupovým tokenem uživatele
.
## Cesta GET – vykreslit jako webovou stránku
Chcete-li vrátit HTML místo JSON, zabalte tělo do `Response` pomocí
`Content-Type` hlavičky. Tato cesta je veřejná (`isAuthRequd: false`), takže
generovaný dokument může být sdílen jako odkaz.
```ts filename="src/logic-functions/view-document.ts"
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
import { Response } from 'twenty-sdk/logic-function';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { documentHtmlPage } from 'src/utils/render-document';
const htmlResponse = (html: string, status = 200): Response =>
new Response(html, { status, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
const handler = async (event: RoutePayload): Promise<Response> => {
const documentId = event.queryStringParameters?.id;
if (!documentId) {
return htmlResponse(documentHtmlPage('Missing document id', 'Provide ?id=<documentId>.'), 400);
}
// Filtered list query so an unknown id renders a clean 404 page instead of throwing.
const { documents } = await new CoreApiClient().query({
documents: {
__args: { filter: { id: { eq: documentId } }, first: 1 },
edges: { node: { id: true, name: true, content: true } },
},
});
const document = documents?.edges?.[0]?.node;
if (!document?.id) {
return htmlResponse(documentHtmlPage('Document not found', `No document with id ${documentId}.`), 404);
}
return htmlResponse(documentHtmlPage(document.name ?? 'Document', document.content ?? ''));
};
export default defineLogicFunction({
universalIdentifier: VIEW_DOCUMENT_ROUTE_UNIVERSAL_IDENTIFIER,
name: 'view-document',
timeoutSeconds: 15,
handler,
httpRouteTriggerSettings: {
path: '/documents/view',
httpMethod: 'GET',
isAuthRequired: false,
},
});
```
`documentHtmlPage` vykresluje Markdown tělo na HTML (s [marked](https://marked.js.org/),
zmaskoval) a klesne do čistého, vytisknutelná stránka, která zobrazuje pouze obsah šablony
– stejný vzhled jako PDF a náhled v aplikaci.
[Viz pomocník] (https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/render-document.ts).
## Vyzkoušejte
Pomocí šablony a osoby ve vašem pracovním prostoru zavolejte na trasu (získejte token z
**Nastavení → API a Webhooks**):
```bash filename="Terminal"
curl -X POST http://localhost:2020/s/documents/generate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"templateId":"<templateId>","recordId":"<personId>"}'
# → {"success":true,"documentId":"...","content":"Dear Jeffery Griffin, ..."}
```
Otevřete vrácený dokument ve vašem prohlížeči:
```
http://localhost:2020/s/documents/view?id=<documentId>
```
<Frame caption="Veřejná trasa GET vykresluje dokument jako tiskovou stránku.">
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="Vykreslená webová stránka dokumentu" />
</Frame>
<Tip>
Během testování s
`yarn twenty dev:function:logs`, nebo vyvolat přímo s
`yarn twenty dev:exec`.
</Tip>
**Po tomto kroku:** aplikace může generovat dokumenty přes HTTP a sloužit jako
webové stránky. Nyní ho použijeme bez `curl`.
<Card title="Další: budování UI →" icon="table-columns" href="/l/cs/developers/extend/apps/tutorials/document-generator/building-the-ui">
Zobrazení, navigace, příkaz a přední součást.
</Card>

View File

@@ -0,0 +1,66 @@
---
title: "Tutoriál: Generátor dokumentů"
icon: wand-magic-sparkles
description: Vytvořte skutečnou aplikaci Twenty, která generuje personalizované dokumenty z vašich CRM dat.
---
V tomto tutoriálu vytvoříte **Generátor dokumentů** — aplikaci, která převádí znovupoužitelné šablony na personalizované dokumenty pomocí dat, která už máte ve svém CRM.
Napište šablonu jednou pomocí `{{placeholders}}` a poté vygenerujte vyplněný dokument pro libovolnou osobu nebo společnost jedním kliknutím — z příkazové nabídky, od AI agenta nebo z workflow.
<Frame caption="Jeden šablonový dokument, vygenerovaný pro konkrétní osobu, otevřený jako stránka připravená k tisku.">
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="Vygenerovaný dokument obchodní nabídky" />
</Frame>
## Co se naučíte
Každá kapitola přidá jednu schopnost. Na konci se seznámíte s většinou SDK.
| Kapitola | Schopnost | Referenční dokumentace |
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [1. Datový model](/l/cs/developers/extend/apps/tutorials/document-generator/data-model) | Objekty, pole a relace | [Data](/l/cs/developers/extend/apps/data/overview) |
| [2. Generování dokumentů](/l/cs/developers/extend/apps/tutorials/document-generator/generating-documents) | Logická funkce (nástroj AI + akce workflow), která vyplní šablonu v Markdownu a připojí vyladěné PDF | [Logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) |
| [3. HTTP trasy](/l/cs/developers/extend/apps/tutorials/document-generator/http-routes) | Poskytování JSONu a sdílitelné HTML stránky z tras | [Logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) |
| [4. Vytváření uživatelského rozhraní](/l/cs/developers/extend/apps/tutorials/document-generator/building-the-ui) | Zobrazení, navigace, příkazová nabídka a frontendové komponenty, které zobrazují náhled dokumentu a upravují šablonu | [Rozvržení](/l/cs/developers/extend/apps/layout/overview) |
| [5. Agent AI](/l/cs/developers/extend/apps/tutorials/document-generator/ai-agent) | Agent + dovednost | [Dovednosti a agenti](/l/cs/developers/extend/apps/logic/skills-and-agents) |
| [6. Publikování](/l/cs/developers/extend/apps/tutorials/document-generator/publishing) | Publikujte ji na marketplace | [Publikování](/l/cs/developers/extend/apps/operations/publishing) |
## Předpoklady
Měli byste mít hotový [rychlý start](/l/cs/developers/extend/apps/getting-started/quick-start):
lokální server Twenty běžící na portu `2020` a CLI k němu přihlášené.
Pokud ne, nyní si jeden vytvořte a spusťte:
```bash filename="Terminal"
npx create-twenty-app@latest document-generator
cd document-generator
yarn twenty dev
```
<Note>
Raději byste si přečetli hotový kód? Celá aplikace je v
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
Každý úryvek níže je z ní zkopírovaný.
</Note>
## Jak do sebe aplikace zapadá
<Frame>
<img src="/images/docs/developers/extends/apps/document-generator/how-it-fits.svg" alt="Šablona se zástupnými symboly se převede na vyladěný dokument s PDF souborem; generování lze spustit z příkazové nabídky, agenta AI, workflow nebo pomocí sdílitelného odkazu" />
</Frame>
**Šablonu** napíšete jednou v rich-text editoru pomocí `{{placeholders}}`. Výběr
šablony a CRM záznamu vyplní zástupné symboly a uloží vyladěný
**dokument** (s PDF souborem). Všechno ostatní — příkazová nabídka, agent AI,
krok workflow, sdílený odkaz — je jen jiný způsob, jak spustit ten
jeden generátor.
## Udržujte tento cyklus v chodu
Nechte `yarn twenty dev` běžet v terminálu po celý tutoriál. Pokaždé, když
přidáte nebo upravíte soubor pod `src/`, během několika sekund se znovu synchronizuje s vaším serverem, takže můžete sledovat, jak se každá schopnost objevuje v uživatelském rozhraní, jak ji vytváříte.
<Card title="Začněte vytvářet →" icon="database" href="/l/cs/developers/extend/apps/tutorials/document-generator/data-model">
Kapitola 1: modelujte dokumenty a šablony.
</Card>

View File

@@ -0,0 +1,137 @@
---
title: 6. Publikování
icon: rocket
description: Přidejte metadata tržiště a publikujte svou aplikaci.
---
Vaše aplikace funguje. Posledním krokem je popsat jej pro tržiště a publikovat.
## Přidat metadata tržiště
[application config](/l/cs/developers/extend/apps/config/application) nese
identitu, která se objeví v tržišti: autor, kategorie, logo a podpora
. Umístěte logo do `public/` a odkažte se na něj pomocí `logo`.
```ts filename="src/application-config.ts"
import { defineApplication } from 'twenty-sdk/define';
export default defineApplication({
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
displayName: 'Document Generator',
description:
'Create reusable document templates and generate personalized documents from your CRM records.',
logo: 'public/document-generator.svg',
author: 'Twenty',
category: 'Productivity',
websiteUrl: 'https://docs.twenty.com/l/cs/developers/extend/apps',
termsUrl: 'https://www.twenty.com/terms',
emailSupport: 'contact@twenty.com',
issueReportUrl: 'https://github.com/twentyhq/twenty/issues',
});
```
<Tip>
Výchozí role je deklarována s `defineApplicationRole()` ve svém vlastním souboru – vy
zde již neprojdete `defaultRoleUniversalIdentifier`.
</Tip>
Přidejte také klíčové slovo `dvacet app` do `package.json` tak, aby byla aplikace nalezena:
```json filename="package.json"
{ "keywords": ["twenty-app"] }
```
## Přidat galerii snímků obrazovky
tržiště se prodává se snímky obrazovky. Vložte několik souborů PNG do
`public/gallery/` a odkažte se na ně pomocí `galleryImages` — na stránce s výpisem se zobrazí jako
galerie v pořadí, v jakém jsou v poli.
```ts filename="src/application-config.ts"
export default defineApplication({
// ...identity from above
galleryImages: [
'public/gallery/01-generated-document.png',
'public/gallery/02-command-menu.png',
'public/gallery/03-template-editor.png',
'public/gallery/04-documents.png',
],
});
```
<Tip>
Lead with the payoff: make the first screenshot the finished result (generated
document), then show how it is triggered and authored. (Automatic Copy) Použít křiklavé, vysoké rozlišení
snímky — jsou to první věc, kterou uživatel vidí.
</Tip>
Dejte `README.md` stejné ošetření — je to přední stránka na npm a GitHub.
Otevřete pomocí nabídky hodnot a snímku obrazovky, vyberte titulky,
ponechte podrobnosti sestavení pod složkou.
## Zkontrolovat před odesláním
Spustit tytéž brány CI:
```bash filename="Terminal"
yarn lint # oxlint
yarn typecheck # tsgo
yarn test:unit # unit tests
yarn twenty plan # preview the metadata diff
```
Plán vypíše přesně to, co by se na serveru změnilo, aniž by změny provedl —
dobrá závěrečná kontrola. Viz
[Testing](/l/cs/developers/extend/apps/operations/testing) a
[Synchronizace a obnovy](/l/cs/developers/extend/apps/operations/sync-and-recovery).
## Publikovat
```bash filename="Terminal"
# Public app → npm (default)
yarn twenty app:publish
# Or deploy privately to a specific server's registry
yarn twenty app:publish --private -r <remote>
```
`app:publish` ve výchozím nastavení sestavuje a publikuje do npm; `--private` nahraje
tarball do privátního registru 20 serverů. Chcete-li zobrazit publikovanou aplikaci
v obchodě instance, spustí synchronizaci katalogu:
```bash filename="Terminal"
yarn twenty dev:catalog-sync -r <remote>
```
Úplné podrobnosti a kontrolní seznam vydání:
[Publishing](/l/cs/developers/extend/apps/operations/publishing).
## Vytvořili jste aplikaci 🎉
V šesti kapitolách jste použili většinu povrchu SDK:
* **Objekty, pole a vztahy** modelovat data
* **Logická funkce** obklopená jako **nástroj AI**, **akce pracovního postupu** a **cesty HTTP**
* **Zobrazení, navigace, příkaz a přední složka** pro UI
* **agent + dovednost** pro generování přirozeného jazyka
* **Metadata tržiště** a tok zveřejnění
Dokončená aplikace je na
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
## Kde jít dál
<CardGroup cols={2}>
<Card title="Odkaz na údaje" icon="database" href="/l/cs/developers/extend/apps/data/overview">
Každý typ pole, vztah a index.
</Card>
<Card title="Logický odkaz" icon="bolt" href="/l/cs/developers/extend/apps/logic/overview">
Cron a databázová událost, klíčový obchod, připojení OAuth.
</Card>
<Card title="Odkaz na rozložení" icon="table-columns" href="/l/cs/developers/extend/apps/layout/overview">
Rozvržení stránky, widgety hlavního panelu a další povrchy uživatelského rozhraní.
</Card>
<Card title="Operace" icon="rocket" href="/l/cs/developers/extend/apps/operations/overview">
CLI, testování, dálkové ovládání a CI.
</Card>
</CardGroup>

View File

@@ -0,0 +1,147 @@
---
title: API
description: Programově dotazujte a upravujte svá CRM data pomocí REST nebo GraphQL.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
Twenty bylo vytvořeno s ohledem na vývojáře, nabízíme výkonné API, které se přizpůsobí vašemu vlastnímu datovému modelu. Nabízíme čtyři typy API, které splňují různé potřeby integrace.
## Přístup orientovaný na vývojáře
Twenty generuje API specificky pro váš datový model:
* **Nejsou vyžadována dlouhá ID**: Používejte v koncových bodech přímo názvy objektů a polí.
* **Standardní a vlastní objekty jsou rovnocenně zpracovány**: Vaše vlastní objekty mají stejnou podporu API jako vestavěné.
* **Vyhrazené koncové body**: Každý objekt a pole má svůj vlastní koncový bod API.
* **Vlastní dokumentace**: Generována specificky pro datový model vašeho pracovního prostoru.
<Note>
Vaše personalizovaná dokumentace k API je dostupná v **Nastavení → API & Webhooks** po vytvoření API klíče. Protože Twenty generuje API odpovídající vašemu vlastnímu datovému modelu, dokumentace je jedinečná pro váš pracovní prostor.
</Note>
## Dva typy API
### Core API
Přístupné na `/rest/` nebo `/graphql/`
Pracujte se svými skutečnými **záznamy** (daty):
* Vytvářejte, čtěte, aktualizujte a mazejte Lidi, Společnosti, Příležitosti atd.
* Dotazujte a filtrujte data
* Spravování vztahů mezi záznamy.
### Metadata API
Přístupné na `/rest/metadata/` nebo `/metadata/`
Spravujte svůj **pracovní prostor a datový model**:
* Vytvářet, upravovat nebo mazat objekty a pole.
* Konfigurace nastavení pracovního prostoru.
* Definujte vztahy mezi objekty
## REST vs GraphQL
Jak Core, tak Metadata API jsou k dispozici ve formátech REST a GraphQL:
| Formát | Dostupné operace |
| ----------- | ---------------------------------------------------------------------- |
| **REST** | CRUD, hromadné operace, operace upsert |
| **GraphQL** | Stejné + **hromadné operace upsert**, dotazy na vztahy v jednom volání |
Zvolte podle svých potřeb — oba formáty přistupují ke stejným datům.
## Koncové body API
| Prostředí | Základní URL |
| ------------------- | ------------------------- |
| **Cloud** | `https://api.twenty.com/` |
| **Vlastní hosting** | `https://{your-domain}/` |
## Ověření
Každý požadavek na API vyžaduje klíč API v hlavičce:
```
Authorization: Bearer YOUR_API_KEY
```
### Vytvořit API klíč
1. Přejděte na **Nastavení → APIs & Webhooks**
2. Klikněte na **+ Vytvořit klíč**
3. Nakonfigurujte:
* **Název**: Popisný název pro klíč
* **Datum vypršení platnosti**: Kdy klíč vyprší
4. Klikněte na **Uložit**
5. **Zkopírujte ihned** — klíč se zobrazí pouze jednou
<VimeoEmbed videoId="928786722" title="Vytvoření klíče API" />
<Warning>
Váš klíč API poskytuje přístup k citlivým datům. Nesdílejte ho s nedůvěryhodnými službami. Pokud je kompromitován, okamžitě ho deaktivujte a vygenerujte nový.
</Warning>
### Přiřaďte roli klíči API
Pro vyšší bezpečnost přiřaďte konkrétní roli, abyste omezili přístup:
1. Přejděte na **Nastavení → Členové → Role**
2. Klikněte na roli, kterou chcete přiřadit
3. Otevřete záložku **Přiřazení**
4. V části **API Keys** klikněte na **+ Přiřadit ke klíči API**
5. Vyberte klíč API
Klíč zdědí oprávnění této role. Podrobnosti viz [Oprávnění](/l/cs/user-guide/permissions-access/capabilities/permissions).
### Spravovat API klíče
**Znovu vygenerovat**: Nastavení → APIs & Webhooks → Klikněte na klíč → **Znovu vygenerovat**
**Smazat**: Nastavení → APIs & Webhooks → Klikněte na klíč → **Smazat**
## API Playground
Testujte svá API přímo v prohlížeči pomocí našeho vestavěného playgroundu — k dispozici pro **REST** i **GraphQL**.
### Přístup do Playgroundu
1. Přejděte na **Nastavení → APIs & Webhooks**
2. Vytvořte klíč API (povinné)
3. Klikněte na **REST API** nebo **GraphQL API** pro otevření playgroundu
### Co získáte
* **Interaktivní dokumentace**: Generována pro váš specifický datový model
* **Živé testování**: Spouštějte reálná volání API vůči vašemu pracovnímu prostoru
* **Průzkumník schématu**: Procházejte dostupné objekty, pole a vztahy
* **Tvůrce požadavků**: Sestavujte dotazy s automatickým doplňováním
Playground odráží vaše vlastní objekty a pole, takže dokumentace je pro váš pracovní prostor vždy přesná.
## Hromadné operace
REST i GraphQL podporují hromadné operace:
* **Velikost dávky**: Až 60 záznamů na požadavek.
* **Operace**: Vytváření, aktualizace a mazání více záznamů
**Funkce pouze pro GraphQL:**
* **Hromadný upsert**: Vytvoření nebo aktualizace v jednom volání
* Používejte množná čísla názvů objektů (např. `CreateCompanies` místo `CreateCompany`)
## Limity rychlosti
Požadavky na API jsou omezovány, aby byla zajištěna stabilita platformy:
| Limit | Hodnota |
| ------------------ | -------------------- |
| **Požadavky** | 100 volání za minutu |
| **Velikost dávky** | 60 záznamů na volání |
<Tip>
Pro maximalizaci propustnosti používejte hromadné operace — zpracujte až 60 záznamů v jediném volání API místo odesílání jednotlivých požadavků.
</Tip>

View File

@@ -0,0 +1,112 @@
---
title: Webhooky
description: Dostávejte oznámení v reálném čase, když ve vašem CRM dojde k událostem.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
Webhooky posílají data do vašich systémů v reálném čase, když v Twenty dojde k událostem — bez potřeby průběžného dotazování. Použijte je k udržování externích systémů v synchronizaci, spouštění automatizací nebo zasílání upozornění.
## Vytvořit Webhook
1. Přejděte na **Nastavení → API & Webhooks → Webhooks**
2. Klikněte na **+ Vytvořit webhook**
3. Zadejte adresu URL svého webhooku (musí být veřejně přístupná)
4. Klikněte na **Uložit**
Webhook se aktivuje okamžitě a začne odesílat oznámení.
<VimeoEmbed videoId="928786708" title="Vytvoření webhooku" />
### Spravovat Webhooky
**Upravit**: Klikněte na webhook → Aktualizovat adresu URL → **Uložit**
**Odstranit**: Klikněte na webhook → **Odstranit** → Potvrdit
## Události
Twenty odesílá webhooky pro tyto typy událostí:
| Událost | Příklad |
| -------------------------- | ---------------------------------------------------------- |
| **Záznam je vytvořen** | `person.created`, `company.created`, `note.created` |
| **Záznam je aktualizován** | `person.updated`, `company.updated`, `opportunity.updated` |
| **Záznam je odstraněn** | `person.deleted`, `company.deleted` |
Všechny typy událostí jsou odesílány na adresu URL vašeho webhooku. Filtrování událostí může být přidáno v budoucích verzích.
## Formát payloadu
Každý webhook odesílá HTTP POST s tělem ve formátu JSON:
```json
{
"event": "person.created",
"data": {
"id": "abc12345",
"firstName": "Alice",
"lastName": "Doe",
"email": "alice@example.com",
"createdAt": "2025-02-10T15:30:45Z",
"createdBy": "user_123"
},
"timestamp": "2025-02-10T15:30:50Z"
}
```
| Pole | Popis |
| ---------------- | ------------------------------------------------------ |
| `událost` | Co se stalo (např. `person.created`) |
| `data` | Celý záznam, který byl vytvořen/aktualizován/odstraněn |
| `časové razítko` | Kdy k události došlo (UTC) |
<Note>
Odpovězte se **stavem HTTP 2xx** (200–299) pro potvrzení přijetí. Odpovědi mimo 2xx se zaznamenávají jako selhání doručení.
</Note>
## Ověření Webhooku
Twenty kvůli bezpečnosti podepisuje každý požadavek webhooku. Ověřujte podpisy, abyste zajistili, že požadavky jsou autentické.
### Záhlaví
| Hlavička | Popis |
| ---------------------------- | ------------------------ |
| `X-Twenty-Webhook-Signature` | Podpis HMAC SHA256 |
| `X-Twenty-Webhook-Timestamp` | Časové razítko požadavku |
### Kroky ověření
1. Získejte časové razítko z `X-Twenty-Webhook-Timestamp`
2. Vytvořte řetězec: `{timestamp}:{JSON payload}`
3. Vypočítejte HMAC SHA256 pomocí vašeho tajného klíče webhooku
4. Porovnejte s `X-Twenty-Webhook-Signature`
### Příklad (Node.js)
```javascript
const crypto = require("crypto");
const timestamp = req.headers["x-twenty-webhook-timestamp"];
const payload = JSON.stringify(req.body);
const secret = "your-webhook-secret";
const stringToSign = `${timestamp}:${payload}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(stringToSign)
.digest("hex");
const isValid = expectedSignature === req.headers["x-twenty-webhook-signature"];
```
## Webhooky vs. pracovní postupy
| Metoda | Směr | Případ použití |
| ----------------------------------------- | ---- | --------------------------------------------------------------- |
| **Webhooky** | OUT | Automaticky informujte externí systémy o jakékoli změně záznamu |
| **Pracovní postup + požadavek HTTP** | OUT | Odesílejte data ven s vlastní logikou (filtry, transformace) |
| **Spouštěč webhooku v pracovním postupu** | IN | Přijímejte data do Twenty z externích systémů |
Pro příjem externích dat viz [Nastavení spouštěče webhooku](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).

View File

@@ -0,0 +1,32 @@
---
title: Rozšiřte
description: Rozšiřte funkčnost Twenty pomocí rozhraní API, webhooků a vlastních aplikací.
---
<Frame>
<img src="/images/user-guide/integrations/plug.png" alt="AI" />
</Frame>
## Přehled
Twenty je navrženo tak, aby bylo rozšiřitelné. Použijte naše rozhraní API, webhooky a aplikační framework k integraci se svými stávajícími nástroji a k vytváření vlastní funkčnosti.
## Co můžete dělat
* **API**: Programově dotazujte a upravujte svá CRM data pomocí REST nebo GraphQL
* **Webhooky**: Dostávejte oznámení v reálném čase, když v Twenty dojde k událostem
* **Aplikace**: Vytvářejte vlastní aplikace, které rozšiřují možnosti Twenty
## Začínáme
<CardGroup cols={2}>
<Card title="API" icon="code" href="/l/cs/developers/extend/api">
Programově se připojte k Twenty
</Card>
<Card title="Webhooky" icon="bell" href="/l/cs/developers/extend/webhooks">
Dostávejte oznámení o událostech v reálném čase
</Card>
<Card title="Aplikace" icon="puzzle-piece" href="/l/cs/developers/extend/apps/getting-started/quick-start">
Vytvářejte přizpůsobení jako kód
</Card>
</CardGroup>

View File

@@ -0,0 +1,189 @@
---
title: OAuth
icon: key
description: Tok s autorizačním kódem s PKCE a přihlašovacími údaji klienta pro přístup server-to-server.
---
Twenty implementuje OAuth 2.0 s autorizačním kódem + PKCE pro aplikace pro uživatele a přihlašovací údaje klienta pro přístup server-to-server. Klienti se registrují dynamicky přes [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — žádné ruční nastavení v dashboardu.
## Kdy použít OAuth
| Scénář | Metoda ověřování |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Interní skripty, automatizace | [Klíč API](/l/cs/developers/extend/api#authentication) |
| Externí aplikace jednající jménem uživatele | **OAuth — autorizační kód** |
| Server-to-server, bez uživatelského kontextu | **OAuth — přihlašovací údaje klienta** |
| Aplikace Twenty s rozšířeními uživatelského rozhraní | [Aplikace](/l/cs/developers/extend/apps/getting-started/quick-start) (OAuth je řešen automaticky) |
## Registrace klienta
Twenty podporuje **dynamickou registraci klienta** podle [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). Není potřeba žádné ruční nastavení — registrujte programově:
```bash
POST /oauth/register
Content-Type: application/json
{
"client_name": "My Integration",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code"],
"token_endpoint_auth_method": "client_secret_post"
}
```
**Odpověď:**
```json
{
"client_id": "abc123",
"client_secret": "secret456",
"client_name": "My Integration",
"redirect_uris": ["https://myapp.com/callback"]
}
```
<Warning>
Uložte `client_secret` bezpečně — později jej nelze získat zpět.
</Warning>
## Oprávnění
| Oprávnění | Přístup |
| --------- | ----------------------------------------------------- |
| `api` | Úplný přístup pro čtení i zápis k API Core a Metadata |
| `profile` | Čtení informací o profilu ověřeného uživatele |
Vyžádejte oprávnění jako řetězec oddělený mezerami: `scope=api profile`
## Tok s autorizačním kódem
Tento tok použijte, když vaše aplikace jedná jménem uživatele Twenty.
### 1. Přesměrujte uživatele k autorizaci
```
GET /oauth/authorize?
client_id=YOUR_CLIENT_ID&
response_type=code&
redirect_uri=https://myapp.com/callback&
scope=api&
state=random_state_value&
code_challenge=CHALLENGE&
code_challenge_method=S256
```
| Parametr | Povinné | Popis |
| ----------------------- | ---------- | -------------------------------------------------------------- |
| `client_id` | Ano | Vaše registrované ID klienta |
| `response_type` | Ano | Musí být `code` |
| `redirect_uri` | Ano | Musí odpovídat registrované adrese URI pro přesměrování |
| `scope` | Ne | Oprávnění oddělená mezerami (výchozí je `api`) |
| `state` | Doporučeno | Náhodný řetězec k prevenci útoků CSRF |
| `code_challenge` | Doporučeno | Výzva PKCE (hash SHA-256 z verifieru, kódovaný jako base64url) |
| `code_challenge_method` | Doporučeno | Při použití PKCE musí být `S256` |
Uživatel uvidí souhlasovou obrazovku a přístup schválí nebo zamítne.
### 2. Zpracujte callback
Po autorizaci Twenty přesměruje zpět na vaše `redirect_uri`:
```
https://myapp.com/callback?code=AUTH_CODE&state=random_state_value
```
Ověřte, že `state` odpovídá tomu, co jste poslali.
### 3. Vyměňte kód za tokeny
```bash
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=https://myapp.com/callback&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
code_verifier=YOUR_PKCE_VERIFIER
```
**Odpověď:**
```json
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBh..."
}
```
### 4. Použijte přístupový token
```bash
GET /rest/companies
Authorization: Bearer ACCESS_TOKEN
```
### 5. Obnovte po vypršení platnosti
```bash
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=YOUR_REFRESH_TOKEN&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET
```
## Tok s přihlašovacími údaji klienta
Pro integrace server-to-server bez interakce uživatele:
```bash
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
scope=api
```
Vrácený token má přístup na úrovni pracovního prostoru, není vázán na žádného konkrétního uživatele.
## Zjišťování serveru
Twenty zveřejňuje svou konfiguraci OAuth na standardním koncovém bodu pro zjišťování:
```
GET /.well-known/oauth-authorization-server
```
Vrací všechny koncové body, podporované typy grantů, oprávnění a možnosti — užitečné pro tvorbu obecných klientů OAuth.
## Přehled koncových bodů API
| Koncový bod | Účel |
| ----------------------------------------- | ---------------------------- |
| `/.well-known/oauth-authorization-server` | Zjišťování metadat serveru |
| `/oauth/register` | Dynamická registrace klienta |
| `/oauth/authorize` | Autorizace uživatele |
| `/oauth/token` | Výměna a obnovení tokenu |
| Prostředí | Základní URL |
| ------------------- | ------------------------ |
| **Cloud** | `https://api.twenty.com` |
| **Vlastní hosting** | `https://{your-domain}` |
## OAuth vs klíče API
| | API Klíče | OAuth |
| --------------------------- | ------------------------------------- | ------------------------------------------- |
| **Nastavení** | Generovat v Nastavení | Zaregistrovat klienta, implementovat tok |
| **Uživatelský kontext** | Žádný (na úrovni pracovního prostoru) | Oprávnění konkrétního uživatele |
| **Vhodné pro** | Skripty, interní nástroje | Externí aplikace, víceuživatelské integrace |
| **Rotace tokenů** | Ruční | Automaticky prostřednictvím refresh tokenů |
| **Přístup podle oprávnění** | Plný přístup k API | Jemně odstupňovaný pomocí oprávnění |

View File

@@ -0,0 +1,117 @@
---
title: Webhooky
icon: satellite-dish
description: Dostávejte upozornění při změnách záznamů — HTTP POST na váš koncový bod při každém vytvoření, aktualizaci nebo smazání.
---
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
Twenty odešle HTTP POST na vaši adresu URL pokaždé, když je záznam vytvořen, aktualizován nebo smazán. Všechny typy objektů jsou podporovány, včetně vlastních objektů.
## Vytvořit Webhook
1. Přejděte na **Nastavení → API & Webhooks → Webhooks**
2. Klikněte na **+ Vytvořit webhook**
3. Zadejte adresu URL svého webhooku (musí být veřejně přístupná)
4. Klikněte na **Uložit**
Webhook se aktivuje okamžitě a začne odesílat oznámení.
<VimeoEmbed videoId="928786708" title="Vytvoření webhooku" />
### Spravovat Webhooky
**Upravit**: Klikněte na webhook → Aktualizovat adresu URL → **Uložit**
**Odstranit**: Klikněte na webhook → **Odstranit** → Potvrdit
## Události
Twenty odesílá webhooky pro tyto typy událostí:
| Událost | Příklad |
| -------------------------- | ---------------------------------------------------------- |
| **Záznam je vytvořen** | `person.created`, `company.created`, `note.created` |
| **Záznam je aktualizován** | `person.updated`, `company.updated`, `opportunity.updated` |
| **Záznam je odstraněn** | `person.deleted`, `company.deleted` |
Všechny typy událostí jsou odesílány na adresu URL vašeho webhooku. Filtrování událostí může být přidáno v budoucích verzích.
## Formát payloadu
Každý webhook odesílá HTTP POST s tělem ve formátu JSON:
```json
{
"event": "person.created",
"data": {
"id": "abc12345",
"firstName": "Alice",
"lastName": "Doe",
"email": "alice@example.com",
"createdAt": "2025-02-10T15:30:45Z",
"createdBy": "user_123"
},
"timestamp": "2025-02-10T15:30:50Z"
}
```
| Pole | Popis |
| ----------- | ------------------------------------------------------ |
| `event` | Co se stalo (např. `person.created`) |
| `data` | Celý záznam, který byl vytvořen/aktualizován/odstraněn |
| `timestamp` | Kdy k události došlo (UTC) |
<Note>
Odpovězte se **stavem HTTP 2xx** (200–299) pro potvrzení přijetí. Odpovědi mimo 2xx se zaznamenávají jako selhání doručení.
</Note>
## Ověření Webhooku
Twenty kvůli bezpečnosti podepisuje každý požadavek webhooku. Ověřujte podpisy, abyste zajistili, že požadavky jsou autentické.
### Záhlaví
| Hlavička | Popis |
| ---------------------------- | ------------------------ |
| `X-Twenty-Webhook-Signature` | Podpis HMAC SHA256 |
| `X-Twenty-Webhook-Timestamp` | Časové razítko požadavku |
### Kroky ověření
1. Získejte časové razítko z `X-Twenty-Webhook-Timestamp`
2. Vytvořte řetězec: `{timestamp}:{JSON payload}`
3. Vypočítejte HMAC SHA256 pomocí vašeho tajného klíče webhooku
4. Porovnejte s `X-Twenty-Webhook-Signature`
### Příklad (Node.js)
```javascript
const crypto = require("crypto");
const timestamp = req.headers["x-twenty-webhook-timestamp"];
const payload = JSON.stringify(req.body);
const secret = "your-webhook-secret";
const stringToSign = `${timestamp}:${payload}`;
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(stringToSign)
.digest("hex");
const receivedSignature = req.headers["x-twenty-webhook-signature"];
const isValid = crypto.timingSafeEqual(
Buffer.from(expectedSignature, "hex"),
Buffer.from(receivedSignature, "hex")
);
```
## Webhooky vs. pracovní postupy
| Metoda | Směr | Případ použití |
| ----------------------------------------- | ---- | --------------------------------------------------------------- |
| **Webhooky** | OUT | Automaticky informujte externí systémy o jakékoli změně záznamu |
| **Pracovní postup + požadavek HTTP** | OUT | Odesílejte data ven s vlastní logikou (filtry, transformace) |
| **Spouštěč webhooku v pracovním postupu** | IN | Přijímejte data do Twenty z externích systémů |
Pro příjem externích dat viz [Nastavení spouštěče webhooku](/l/cs/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger).

View File

@@ -0,0 +1,28 @@
---
title: Vývojáři
description: Vytvářejte aplikace, používejte API, hostujte sami nebo přispívejte do kódu.
---
import { CardTitle } from "/snippets/card-title.mdx"
<CardGroup cols={3}>
<Card href="/l/cs/developers/extend/apps/getting-started/quick-start" img="/images/user-guide/halftone/dev-apps.png">
<CardTitle>Aplikace</CardTitle>
Rozšiřte Twenty pomocí vlastních objektů, serverové logiky, UI komponent a AI agentů — to vše jako balíčky TypeScriptu.
</Card>
<Card href="/l/cs/developers/extend/api" img="/images/user-guide/halftone/dev-api.png">
<CardTitle>API</CardTitle>
Rozhraní API REST a GraphQL, webhooky a OAuth.
</Card>
<Card href="/l/cs/developers/self-host/capabilities/docker-compose" img="/images/user-guide/halftone/dev-self-host.png">
<CardTitle>Hostujte sami</CardTitle>
Provozujte Twenty na vlastní infrastruktuře.
</Card>
<Card href="/l/cs/developers/contribute/capabilities/local-setup" img="/images/user-guide/halftone/dev-contribute.png">
<CardTitle>Přispějte</CardTitle>
Nastavte si monorepo lokálně a odesílejte PR.
</Card>
</CardGroup>

View File

@@ -0,0 +1,262 @@
---
title: Docker Compose
icon: docker
---
<Warning>
Docker kontejnery jsou určeny pro produkční provoz nebo vlastní hostování. Pro přispívání se podívejte na [Lokální nastavení](/l/cs/developers/contribute/capabilities/local-setup).
</Warning>
## Přehled
Tato příručka poskytuje podrobný návod k instalaci a konfiguraci aplikace Twenty pomocí Docker Compose. Cílem je zjednodušit proces a zabránit běžným úskalím, která by mohla narušit vaše nastavení.
**Důležité:** Měňte pouze nastavení výslovně uvedená v tomto průvodci. Změna jiných konfigurací může vést k problémům.
Viz [Nastavení proměnných prostředí](/l/cs/developers/self-host/capabilities/setup) pro pokročilou konfiguraci. Všechny proměnné prostředí musí být deklarovány v souboru `docker-compose.yml` na úrovni serveru a/nebo pracovníka, v závislosti na proměnné.
## Systémové Požadavky
* RAM: Ujistěte se, že vaše prostředí má alespoň 2GB RAM. Nedostatek paměti může způsobit zhroucení procesů.
* Docker & Docker Compose: Ujistěte se, že obě jsou nainstalovány a aktuální.
## Možnost 1: Jednořádkový skript
Nainstalujte nejnovější stabilní verzi Twenty jedním příkazem:
```bash
bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
Pro instalaci konkrétní verze nebo větve vypište:
```bash
VERSION=vx.y.z BRANCH=branch-name bash <(curl -sL https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/scripts/install.sh)
```
* Nahraďte x.y.z požadovaným číslem verze.
* Nahraďte branch-name názvem větve, kterou chcete nainstalovat.
## Možnost 2: Manuální kroky
Postupujte podle těchto kroků pro ruční nastavení.
### Krok 1: Nastavení souboru Prostředí
1. **Vytvořte soubor .env**
Zkopírujte příklad souboru prostředí do nového souboru .env ve vašem pracovním adresáři:
```bash
curl -o .env https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/.env.example
```
2. **Vygenerujte šifrovací klíč**
Spusťte následující příkaz k generování jedinečného náhodného řetězce:
```bash
openssl rand -base64 32
```
**Důležité:** Udržujte tuto hodnotu v tajnosti / nesdílejte ji. Ztráta `ENCRYPTION_KEY` znamená ztrátu přístupu ke všem tajným údajům uloženým v databázi (OAuth tokeny, aplikační proměnné, TOTP tajemství atd.).
3. **Aktualizujte `.env` soubor**
Nahraďte místoblokovou hodnotu ve svém .env souboru vygenerovaným tokenem:
```ini
ENCRYPTION_KEY=random_string
```
Podívejte se na [průvodce rotací klíče](/l/cs/developers/self-host/capabilities/key-rotation) pro pokyny, jak jej rotovat bez prostojů.
4. **Nastavte Heslo pro Postgres**
Aktualizujte hodnotu `PG_DATABASE_PASSWORD` ve vašem .env souboru silným heslem bez speciálních znaků.
```ini
PG_DATABASE_PASSWORD=my_strong_password
```
### Krok 2: Získání souboru Docker Compose
Stáhněte soubor `docker-compose.yml` do svého pracovního adresáře:
```bash
curl -o docker-compose.yml https://raw.githubusercontent.com/twentyhq/twenty/refs/heads/main/packages/twenty-docker/docker-compose.yml
```
### Krok 3: Spuštění aplikace
Spusťte Docker kontejnery:
```bash
docker compose up -d
```
### Krok 4: Přístup k aplikaci
Pokud hostujete twentyCRM na svém vlastním počítači, otevřete svůj prohlížeč a přejděte na stránku [http://localhost:3000](http://localhost:3000).
Pokud to hostujete na serveru, zkontrolujte, že server běží a že je vše v pořádku s
```bash
curl http://localhost:3000
```
## Konfigurace
### Otevření Twenty pro Externí Přístup
Ve výchozím nastavení běží Twenty na `localhost` na portu `3000`. Uživatelské rozhraní určuje adresu API podle původu (origin) prohlížeče, takže se načte z libovolného názvu hostitele, který směřuje na kontejner — přesto však nastavte `SERVER_URL` na svou veřejnou adresu URL: server ji používá pro generované odkazy (e-maily, zpětná volání OAuth) a ke zjištění, zda se používá HTTPS, pro zabezpečené soubory cookie.
#### Porozumění `SERVER_URL`
* **Protokol:** Použijte `http` nebo `https` v závislosti na vašem nastavení.
* Použijte `http` pokud nemáte nastavené SSL.
* Použijte `https` pokud máte SSL nakonfigurované.
* **Doména/IP:** Toto je doménové jméno nebo IP adresa, kde je vaše aplikace přístupná.
* **Port:** Uveďte číslo portu, pokud nepoužíváte výchozí porty (`80` pro `http`, `443` pro `https`).
### Požadavky na SSL
SSL (HTTPS) je potřebné pro správné fungování některých funkcí prohlížeče. I když tyto funkce mohou fungovat během místního vývoje (protože prohlížeče zacházejí s localhost jinak), správná konfigurace SSL je nutná při hostování Twenty na běžné doméně.
Například rozhraní schránky může vyžadovat zabezpečený kontext - některé funkce jako tlačítka kopírování v aplikaci nemusí fungovat bez povoleného HTTPS.
Důrazně doporučujeme nastavit Twenty za reverzní proxy se SSL ukončením pro optimální bezpečnost a funkčnost.
#### Konfigurace `SERVER_URL`
1. **Určete svou přístupovou URL**
* **Bez Reverzní Proxy (Přímý Přístup):**
Pokud přistupujete k aplikaci přímo bez reverzní proxy:
```ini
SERVER_URL=http://your-domain-or-ip:3000
```
* **S Reverzní Proxy (Standardní Porty):**
Pokud používáte reverzní proxy jako Nginx nebo Traefik a máte SSL nakonfigurované:
```ini
SERVER_URL=https://your-domain-or-ip
```
* **S Reverzní Proxy (Vlastní Porty):**
Pokud používáte nestandardní porty:
```ini
SERVER_URL=https://your-domain-or-ip:custom-port
```
2. **Aktualizujte `.env` Soubor**
Otevřete svůj `.env` soubor a aktualizujte `SERVER_URL`:
```ini
SERVER_URL=http(s)://your-domain-or-ip:your-port
```
**Příklady:**
* Přímý přístup bez SSL:
```ini
SERVER_URL=http://123.45.67.89:3000
```
* Přístup přes doménu s SSL:
```ini
SERVER_URL=https://mytwentyapp.com
```
3. **Restartujte Aplikaci**
Pro aplikaci změn restartujte Docker kontejnery:
```bash
docker compose down
docker compose up -d
```
#### Úvahy
* **Konfigurace reverzního proxy:**
Ujistěte se, že váš reverzní proxy přesměruje požadavky na správný interní port (výchozí `3000`). Nakonfigurujte terminaci SSL a všechny potřebné hlavičky.
* **Nastavení firewallu:**
Otevřete nezbytné porty v firewallu, aby byl umožněn externí přístup.
* **Konzistence:**
`SERVER_URL` musí odpovídat způsobu, jakým uživatelé přistupují k vaší aplikaci ve svých prohlížečích.
#### Perzistence
* **Datové svazky:**
Konfigurace Docker Compose používá svazky k ukládání dat pro databázi a úložiště serverů.
* **Bezstavová prostředí:**
Pokud nasazujete do bezstavového prostředí (např. některé cloudové služby), nakonfigurujte externí úložiště pro uchování dat.
## Zálohování a obnovení
Pravidelné zálohy chrání vaše data v CRM před ztrátou.
### Vytvořte zálohu databáze
```bash
docker exec twenty-postgres pg_dump -U postgres twenty > backup_$(date +%Y%m%d).sql
```
### Automatizujte denní zálohy
Přidejte do svého crontabu (`crontab -e`):
```bash
0 2 * * * docker exec twenty-postgres pg_dump -U postgres twenty > /backups/twenty_$(date +\%Y\%m\%d).sql
```
### Obnovte ze zálohy
1. Zastavte aplikaci:
```bash
docker compose stop server worker
```
2. Obnovte databázi:
```bash
docker exec -i twenty-postgres psql -U postgres twenty < backup_20240115.sql
```
3. Restartujte služby:
```bash
docker compose up -d
```
### Osvědčené postupy pro zálohování
* **Pravidelně testujte obnovy** — ověřte, že zálohy skutečně fungují
* **Ukládejte zálohy mimo lokalitu** — použijte cloudové úložiště (S3, GCS, atd.)
* **Šifrujte citlivá data** — chraňte zálohy šifrováním
* **Uchovávejte více kopií** — mějte denní, týdenní a měsíční zálohy
## Řešení potíží
Pokud narazíte na jakýkoli problém, zkontrolujte sekci [Řešení potíží](/l/cs/developers/self-host/capabilities/troubleshooting) pro možná řešení.
## Spravovaný hosting
<Tip>
Nechcete Twenty provozovat a spravovat sami? [Najděte si certifikovaného hostingového partnera Twenty](https://twenty.com/partners/list?categories=HOSTING\&ref=docs-docker-compose), který pro vás zajistí nasazení, hosting a kompletní správu vaší instance od začátku do konce. *(Nebo kontaktujte náš tým: [contact@twenty.com](mailto:contact@twenty.com).)*
</Tip>

View File

@@ -0,0 +1,60 @@
---
title: Rotace klíčů
icon: rotate
---
Twenty má dvě nezávislé rodiny klíčů:
* **Klíče pro podepisování JWT** — asymetrické páry klíčů ES256 (označené `kid`), uložené v `core."signingKey"`, používané k podepisování a ověřování přístupových/obnovovacích tokenů.
* **Šifrovací klíč pro data v klidu** — `ENCRYPTION_KEY`, používaný k šifrování OAuth tokenů, aplikačních proměnných, soukromých klíčů podepisovacích klíčů, citlivých konfiguračních hodnot a TOTP tajemství uvnitř obálky `enc:v2:`.
`APP_SECRET` je starší tajný klíč ponechaný kvůli zpětné kompatibilitě: pokud `ENCRYPTION_KEY` není nastaven, slouží jako záložní řešení pro šifrování dat v klidu i pro soubory cookie relace a stále ověřuje dříve existující HS256 přístupové tokeny. Bude označen jako zastaralý (deprecated).
## Klíče pro podepisování JWT
Každý klíč nese `publicKey` (uchovávaný neomezeně dlouho, aby mohl ověřovat dříve vydané tokeny), zašifrovaný `privateKey` (používaný pouze tehdy, když je klíč aktuální), příznak `isCurrent` (vždy přesně jeden záznam) a volitelné `revokedAt`.
### Rotace aktuálního klíče
Nastavte `SIGNING_KEY_ROTATION_DAYS`, chcete-li funkci povolit: denní cron poté vydá nový klíč jako aktuální, jakmile je stávající starší než zadaný práh. Předchozí klíče *nejsou* odvolány, takže tokeny pod nimi podepsané se dál úspěšně ověřují. Ponechte proměnnou nenastavenou, abyste deaktivovali automatickou rotaci.
<Note>Automatická rotace je k dispozici od verze v2.6+.</Note>
### Odvolání klíče (pouze při úniku / v nouzi)
**Settings → Admin Panel → Signing keys → Revoke** na neaktuálním řádku. Smaže zašifrovaný privátní materiál, nastaví `revokedAt` a odmítne každý existující token podepsaný pod daným `kid`.
## Rotace `ENCRYPTION_KEY`
<Note>Příkaz `secret-encryption:rotate` popsaný níže je k dispozici od verze v2.6+.</Note>
Každá zašifrovaná hodnota je zabalena jako `enc:v2:\<keyId>:\<payload>`, kde `\<keyId>` je 8místný hexadecimální prefix odvozený ze surového klíče. Rotace probíhá online a je možné ji kdykoli znovu spustit.
1. **Vygenerujte nový klíč**: `openssl rand -base64 32`.
2. **Nakonfigurujte oba klíče vedle sebe** v `.env` a poté restartujte:
```ini
ENCRYPTION_KEY=NEW_VALUE
FALLBACK_ENCRYPTION_KEY=OLD_VALUE
```
Nové zápisy používají nový klíč, existující řádky se stále dešifrují pomocí záložního klíče.
3. **Znovu zašifrujte existující řádky**:
```bash
docker exec -it {server_container} yarn command:prod secret-encryption:rotate
```
Příkaz prochází šest míst (`connected-account-tokens`, `application-variable`, `application-registration-variable`, `signing-key-private-keys`, `sensitive-config-storage`, `totp-secrets`). SQL filtr přeskočí řádky, které už jsou na novém `\<keyId>`, takže příkaz je idempotentní: přerušte ho a znovu spusťte podle potřeby. Ukončí se s nenulovým kódem, pokud jakýkoli řádek selže — spusťte znovu pro opakování.
| Přepínač | Popis |
| ---------------------------------------- | -------------------------------------------------------- |
| `-s, --site \<site>` | Omezí běh na jedno místo (site). |
| `-b, --batch-size \<n>` | Počet řádků v dávce (výchozí `200`, maximum `5000`). |
| `-d, --dry-run` | Dešifruje + znovu zašifruje v paměti, přeskočí `UPDATE`. |
4. **Odstraňte záložní klíč** jakmile `--dry-run` ukáže, že nezbývají žádné řádky: odeberte `FALLBACK_ENCRYPTION_KEY` a restartujte.
## Podpora staršího `APP_SECRET`
Starší instance, které nikdy nenastavily `ENCRYPTION_KEY`, používají `APP_SECRET` jako šifrovací klíč pro data v klidu (a jako tajemství pro soubor cookie relace, odvozené z něj). Tato cesta je zachována kvůli zpětné kompatibilitě, ale je **zastaralá (deprecated)** — nastavte vyhrazený `ENCRYPTION_KEY` a podle výše uvedeného postupu rotace proveďte migraci pryč od něj. Samotný `APP_SECRET` zůstává v používání pro ověřování starších HS256 přístupových tokenů.

View File

@@ -0,0 +1,413 @@
---
title: Nastavení
icon: gear
---
# Správa konfigurace
<Warning>
**Instalujete poprvé?** Postupujte podle [průvodce instalací Docker Compose](/l/cs/developers/self-host/capabilities/docker-compose) k rozchození Twenty, a poté se sem vraťte pro konfiguraci.
</Warning>
Twenty nabízí **dvě konfigurační režimy** pro různé potřeby nasazení:
**Přístup k administračnímu panelu:** Pouze uživatelé s administrátorskými právy (`canAccessFullAdminPanel: true`) mohou přistupovat k rozhraní pro konfiguraci.
## 1. Konfigurace Admin panelu (výchozí)
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=true # default
```
**Většina konfigurace probíhá prostřednictvím uživatelského rozhraní** po instalaci:
1. Přistupte ke své instanci Twenty (obvykle `http://localhost:3000`)
2. Jděte na **Nastavení / Admin panel / Konfigurační proměnné**
3. Nakonfigurujte integrace, e-mail, úložiště a další
4. Změny se projeví okamžitě (u nasazení s více kontejnery do 15 sekund)
<Warning>
**Nasazení s více kontejnery:** Při použití databázové konfigurace (`IS_CONFIG_VARIABLES_IN_DB_ENABLED=true`) čtou oba serverové i pracovní kontejnery ze stejné databáze. Změny v administračním panelu se dotýkají obou automaticky, což eliminuje potřebu duplicitních proměnných prostředí mezi kontejnery (s výjimkou infrastrukturních proměnných).
</Warning>
**Co můžete konfigurovat přes administrační panel:**
* **Autentizace** - Google/Microsoft OAuth, nastavení hesla
* **E-mail** - nastavení SMTP, šablony, ověření
* **Úložiště** - konfigurace S3, místní cesty k úložišti
* **Integrace** - Gmail, Google Kalendář, služby Microsoft
* **Pracovní postup & Omezení rychlosti** - limity provádění, škrcení API
* **A ještě mnohem více...**
![Proměnné konfigurace Admin panelu](/images/user-guide/setup/admin-panel-config-variables.png)
<Warning>
Každá proměnná je dokumentována s popisy ve vašem administračním panelu v sekci **Nastavení → Admin panel → Konfigurační proměnné**.
Některá nastavení infrastruktury, jako připojení k databázi (`PG_DATABASE_URL`), URL serveru (`SERVER_URL`) a tajné klíče (`ENCRYPTION_KEY`, `FALLBACK_ENCRYPTION_KEY`), lze konfigurovat pouze prostřednictvím souboru `.env`.
[Kompletní technická referenční příručka →](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts)
</Warning>
## Šifrovací klíče
Twenty používá dva šifrovací klíče dostupné pouze přes proměnné prostředí:
| Proměnná | Účel | Povinné |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `ENCRYPTION_KEY` | Primární klíč používaný k šifrování tajných hodnot „v klidu“ (tokeny OAuth, aplikační proměnné, privátní klíče podepisovacích klíčů, TOTP tajné kódy, citlivé konfigurační hodnoty). | Ano pro nové instalace (starší instalace se mohou místo toho spoléhat na `APP_SECRET` — viz níže) |
| `FALLBACK_ENCRYPTION_KEY` | Klíč používaný pouze k ověřování. Během rotace se nastavuje na *předchozí* hodnotu `ENCRYPTION_KEY`, aby bylo možné dešifrovat stávající řádky. | Pouze během rotace |
Kvůli zpětné kompatibilitě, pokud `ENCRYPTION_KEY` není nastaven, Twenty používá pro šifrování „v klidu“ záložně `APP_SECRET`, což odpovídá původnímu chování starších nasazení. Nové instalace by vždy měly nastavit dedikovaný `ENCRYPTION_KEY`.
Hodnoty vygenerujte pomocí `openssl rand -base64 32` a uložte je na bezpečné místo (správce tajných klíčů, zapečetěná konfigurace apod.). Ztráta `ENCRYPTION_KEY` znamená ztrátu přístupu ke všem tajným hodnotám uloženým v databázi.
Jak provést rotaci `ENCRYPTION_KEY` bez výpadku, najdete v [průvodci rotací klíče](/l/cs/developers/self-host/capabilities/key-rotation).
## 2. Konfigurace pouze přes prostředí
```bash
IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
```
**Všechna konfigurace je spravována prostřednictvím souborů `.env`:**
1. Nastavte `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false` ve vašem souboru `.env`
2. Přidejte všechny konfigurační proměnné do souboru `.env`
3. Restartujte kontejnery, aby se změny projevily
4. Administrační panel zobrazí aktuální hodnoty, ale nemůže je upravovat
## Režim více pracovních prostorů
Ve výchozím nastavení běží Twenty v **režimu jednoho pracovního prostoru** — ideální pro většinu samostatně hostovaných nasazení, kde potřebujete jednu instanci CRM pro svou organizaci.
### Režim jednoho pracovního prostoru (výchozí)
```bash
IS_MULTIWORKSPACE_ENABLED=false # default
```
* Jeden pracovní prostor na instanci Twenty
* První uživatel se automaticky stává administrátorem s plnými oprávněními (`canImpersonate` a `canAccessFullAdminPanel`)
* Po vytvoření prvního pracovního prostoru jsou nové registrace zakázány.
* Jednoduchá struktura URL: `https://your-domain.com`
### Povolení režimu více pracovních prostorů
```bash
IS_MULTIWORKSPACE_ENABLED=true
DEFAULT_SUBDOMAIN=app # default value
```
Povolte režim více pracovních prostorů pro nasazení typu SaaS, kde více nezávislých týmů potřebuje své vlastní pracovní prostory na téže instanci Twenty.
**Klíčové rozdíly oproti režimu jednoho pracovního prostoru:**
* Na téže instanci lze vytvářet více pracovních prostorů
* Každý pracovní prostor získá vlastní subdoménu (např. `sales.your-domain.com`, `marketing.your-domain.com`)
* Uživatelé se registrují a přihlašují na `{DEFAULT_SUBDOMAIN}.your-domain.com` (např. `app.your-domain.com`)
* Žádná automatická administrátorská oprávnění — první uživatel v každém pracovním prostoru je běžný uživatel
* Nastavení specifická pro pracovní prostor, jako je subdoména a vlastní doména, se zpřístupní v nastavení pracovního prostoru
<Warning>
**Nastavení pouze na úrovni prostředí:** `IS_MULTIWORKSPACE_ENABLED` lze konfigurovat pouze prostřednictvím souboru `.env` a vyžaduje restart. Nelze jej změnit prostřednictvím administračního panelu.
</Warning>
### Konfigurace DNS pro více pracovních prostorů
Při použití režimu více pracovních prostorů nakonfigurujte své DNS pomocí zástupného (wildcard) záznamu, aby bylo možné dynamicky vytvářet subdomény:
```
*.your-domain.com -> your-server-ip
```
Tím se umožní automatické směrování subdomén pro nové pracovní prostory bez ruční konfigurace DNS.
### Omezení vytváření pracovních prostorů
V režimu více pracovních prostorů možná budete chtít omezit, kdo může vytvářet nové pracovní prostory:
```bash
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
```
Pokud je povoleno, pouze uživatelé s `canAccessFullAdminPanel` mohou vytvářet další pracovní prostory. Uživatelé si stále mohou při úvodní registraci vytvořit svůj první pracovní prostor.
## Integrace Gmail & Google Kalendář
### Vytvořte projekt Google Cloud
1. Přejděte na [Google Cloud Console](https://console.cloud.google.com/)
2. Vytvořte nový projekt nebo vyberte existující
3. Povolte tyto API:
* [Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com)
* [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com)
* [People API](https://console.cloud.google.com/apis/library/people.googleapis.com)
### Konfigurace OAuth
1. Přejděte na [Pověření](https://console.cloud.google.com/apis/credentials)
2. Vytvořte OAuth 2.0 Client ID
3. Přidejte tyto URI přesměrování:
* `https://{your-domain}/auth/google/redirect` (pro SSO)
* `https://{your-domain}/auth/google-apis/get-access-token` (pro integrace)
### Konfigurace v Twenty
1. Jděte na **Nastavení → Admin panel → Konfigurační proměnné**
2. Najděte sekci **Google Auth**
3. Nastavte tyto proměnné:
* `MESSAGING_PROVIDER_GMAIL_ENABLED=true`
* `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
* `AUTH_GOOGLE_CLIENT_ID={client-id}`
* `AUTH_GOOGLE_CLIENT_SECRET={client-secret}`
* `AUTH_GOOGLE_CALLBACK_URL=https://{your-domain}/auth/google/redirect`
* `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`
<Warning>
**Režim pouze s prostředím:** Pokud nastavíte `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, přidejte tyto proměnné do souboru `.env`
</Warning>
**Požadované rozsahy** (automaticky konfigurované):
[Zobrazit relevantní zdrojový kód](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes.ts#L4-L10)
* `https://www.googleapis.com/auth/calendar.events`
* `https://www.googleapis.com/auth/gmail.readonly`
* `https://www.googleapis.com/auth/profile.emails.read`
### Pokud je vaše aplikace v testovacím režimu
Pokud je vaše aplikace v testovacím režimu, budete muset přidat testovací uživatele do vašeho projektu.
V části [Obrazovka souhlasu s OAuth](https://console.cloud.google.com/apis/credentials/consent) přidejte své testovací uživatele do sekce "Testovací uživatelé".
## Integrace Microsoft 365
<Warning>
Uživatelé musí mít [Licence Microsoft 365](https://admin.microsoft.com/Adminportal/Home), aby mohli používat API Kalendáře a Zpráv. Bez něj nebudou moci synchronizovat svůj účet na Twenty.
</Warning>
### Vytvořte projekt v Microsoft Azure
Budete muset vytvořit projekt v [Microsoft Azure](https://portal.azure.com/#view/Microsoft_AAD_IAM/AppGalleryBladeV2) a získat pověření.
### Povoleňte API
Na konzoli Microsoft Azure povolte následující API v "Povoleních":
* Microsoft Graph: Mail.ReadWrite
* Microsoft Graph: Mail.Send
* Microsoft Graph: Calendars.Read
* Microsoft Graph: User.Read
* Microsoft Graph: openid
* Microsoft Graph: email
* Microsoft Graph: profile
* Microsoft Graph: offline_access
<Note>
"Mail.ReadWrite" a "Mail.Send" jsou povinné pouze tehdy, pokud chcete odesílat e-maily pomocí akcí v našich pracovních postupech. Můžete použít "Mail.Read" místo toho, pokud chcete pouze přijímat e-maily.
</Note>
<Note>
"Calendars.ReadWrite" je povinné, pokud chcete vytvářet události v kalendáři pomocí akcí v našich pracovních postupech. Pokud chcete pouze synchronizovat události kalendáře, použijte "Calendars.Read".
</Note>
### Autorizované URI přesměrování
Musíte přidat následující URI přesměrování do vašeho projektu:
* `https://{your-domain}/auth/microsoft/redirect`, pokud chcete použít Microsoft SSO
* `https://{your-domain}/auth/microsoft-apis/get-access-token`
### Konfigurace v Twenty
1. Jděte na **Nastavení → Admin panel → Konfigurační proměnné**
2. Najděte sekci **Microsoft Auth**
3. Nastavte tyto proměnné:
* `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true`
* `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true`
* `AUTH_MICROSOFT_ENABLED=true`
* `AUTH_MICROSOFT_CLIENT_ID={client-id}`
* `AUTH_MICROSOFT_CLIENT_SECRET={client-secret}`
* `AUTH_MICROSOFT_CALLBACK_URL=https://{your-domain}/auth/microsoft/redirect`
* `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`
<Warning>
**Režim pouze s prostředím:** Pokud nastavíte `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, přidejte tyto proměnné do souboru `.env`
</Warning>
### Konfigurace rozsahů
[Zobrazit relevantní zdrojový kód](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes.ts#L2-L9)
* 'openid'
* 'email'
* 'profil'
* 'offline_access'
* 'Mail.ReadWrite'
* 'Mail.Send'
* 'Calendars.Read'
### Pokud je vaše aplikace v testovacím režimu
Pokud je vaše aplikace v testovacím režimu, budete muset přidat testovací uživatele do vašeho projektu.
Přidejte své testovací uživatele do sekce "Uživatelé a skupiny".
## Pracovní úkoly pro Kalendář & Zprávy
Po konfiguraci integrací Gmail, Google Kalendář nebo Microsoft 365 je třeba spustit úlohy na synchronizaci dat na pozadí.
Zaregistrujte následující opakující se úlohy ve vašem pracovním kontejneru:
```bash
# from your worker container
yarn command:prod cron:messaging:messages-import
yarn command:prod cron:messaging:message-list-fetch
yarn command:prod cron:calendar:calendar-event-list-fetch
yarn command:prod cron:calendar:calendar-events-import
yarn command:prod cron:messaging:ongoing-stale
yarn command:prod cron:calendar:ongoing-stale
yarn command:prod cron:workflow:automated-cron-trigger
```
## Konfigurace Emailu
1. Jděte na **Nastavení → Admin panel → Konfigurační proměnné**
2. Najděte sekci **E-mail**
3. Nakonfigurujte své nastavení SMTP:
<Tabs>
<Tab title="Gmail">
Budete potřebovat zřídit [Heslo aplikace](https://support.google.com/accounts/answer/185833).
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.gmail.com
* EMAIL_SMTP_PORT=465
* EMAIL_SMTP_USER=gmail_email_address
* EMAIL_SMTP_PASSWORD='gmail_app_password'
</Tab>
<Tab title="Office365">
**smtp4dev** je falešný SMTP e-mailový server pro vývoj a testování.
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=smtp.office365.com
* EMAIL_SMTP_PORT=587
* EMAIL_SMTP_USER=office365_email_address
* EMAIL_SMTP_PASSWORD='office365_password'
</Tab>
<Tab title="smtp4dev">
**smtp4dev** je falešný SMTP e-mailový server pro vývoj a testování.
* Spusťte obraz smtp4dev: `docker run --rm -it -p 8090:80 -p 2525:25 rnwood/smtp4dev`
* Přístup k uživatelskému rozhraní smtp4dev zde: [http://localhost:8090](http://localhost:8090)
* Nastavte následující proměnné:
* EMAIL_DRIVER=smtp
* EMAIL_SMTP_HOST=localhost
* EMAIL_SMTP_PORT=2525
</Tab>
</Tabs>
<Warning>
**Režim pouze s prostředím:** Pokud nastavíte `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, přidejte tyto proměnné do souboru `.env`
</Warning>
## Úložiště S3
<Warning>
Ve výchozím nastavení Twenty ukládá nahrané soubory do místního souborového systému. Pro produkční nasazení použijte S3 nebo službu kompatibilní se S3 (MinIO, DigitalOcean Spaces atd.). aby byly soubory zachovány při restartech kontejnerů a bylo možné škálovat napříč více instancemi serveru.
</Warning>
Nastavte `STORAGE_TYPE=S_3` a nakonfigurujte proměnné `STORAGE_S3_*` prostřednictvím administračního panelu nebo `.env`. Viz [referenci config-variables.ts](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts) pro úplný seznam proměnných S3.
Při používání S3 s funkcemi závislými na CORS (např. stahování souborů v prohlížeči) se ujistěte, že váš bucket povoluje origin vašeho frontendu Twenty ve své konfiguraci CORS.
## Logické funkce a interpret kódu
Twenty podporuje logické funkce pro pracovní postupy a interpret kódu pro analýzu dat s využitím AI. Obě spouštějí kód poskytnutý uživatelem a z důvodu zabezpečení vyžadují explicitní konfiguraci.
### Výchozí nastavení zabezpečení
**V produkci (NODE_ENV=production):** Jak logické funkce, tak interpret kódu mají ve výchozím nastavení stav **Zakázáno**. Pokud tyto funkce potřebujete, musíte je explicitně povolit pomocí `LOGIC_FUNCTION_TYPE` a `CODE_INTERPRETER_TYPE`.
**V prostředí vývoje (NODE_ENV=development):** Obě mají ve výchozím nastavení **LOCAL** pro pohodlí při lokálním spuštění.
<Warning>
**Upozornění na zabezpečení:** Místní ovladač (`LOGIC_FUNCTION_TYPE=LOCAL` nebo `CODE_INTERPRETER_TYPE=LOCAL`) spouští kód přímo na hostiteli v procesu Node.js bez sandboxu. Měl by být používán pouze pro důvěryhodný kód při vývoji. Pro produkční nasazení, která pracují s nedůvěryhodným kódem, použijte `LOGIC_FUNCTION_TYPE=LAMBDA` nebo `CODE_INTERPRETER_TYPE=E2B` (se sandboxem), případně je ponechte vypnuté.
</Warning>
### Logické funkce - dostupné ovladače
| Ovladač | Proměnná prostředí | Případ použití | Úroveň zabezpečení |
| --------- | ------------------------------ | ------------------------------------------ | ----------------------------------- |
| Neaktivní | `LOGIC_FUNCTION_TYPE=DISABLED` | Úplně zakázat logické funkce | N/A |
| Lokální | `LOGIC_FUNCTION_TYPE=LOCAL` | Vývojová a důvěryhodná prostředí | Nízká (bez sandboxu) |
| Lambda | `LOGIC_FUNCTION_TYPE=LAMBDA` | Produkční prostředí s nedůvěryhodným kódem | Vysoká (izolace na úrovni hardwaru) |
### Logické funkce - doporučená konfigurace
**Pro vývoj:**
```bash
LOGIC_FUNCTION_TYPE=LOCAL # default when NODE_ENV=development
```
**Pro produkci (AWS):**
```bash
LOGIC_FUNCTION_TYPE=LAMBDA
LOGIC_FUNCTION_LAMBDA_REGION=us-east-1
LOGIC_FUNCTION_LAMBDA_ROLE=arn:aws:iam::123456789:role/your-lambda-role
LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID=your-access-key
LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**Pro zakázání logických funkcí:**
```bash
LOGIC_FUNCTION_TYPE=DISABLED # default when NODE_ENV=production
```
### Interpret kódu - dostupné ovladače
| Ovladač | Proměnná prostředí | Případ použití | Úroveň zabezpečení |
| --------- | -------------------------------- | ----------------------------------------------- | -------------------------- |
| Neaktivní | `CODE_INTERPRETER_TYPE=DISABLED` | Zakázat spouštění kódu AI | N/A |
| Lokální | `CODE_INTERPRETER_TYPE=LOCAL` | Pouze pro vývoj | Nízká (bez sandboxu) |
| E2B | `CODE_INTERPRETER_TYPE=E_2_B` | Produkční prostředí se sandboxovaným spouštěním | Vysoká (izolovaný sandbox) |
<Note>
Při použití `LOGIC_FUNCTION_TYPE=DISABLED` nebo `CODE_INTERPRETER_TYPE=DISABLED` vrátí každý pokus o spuštění chybu. To je užitečné, pokud chcete provozovat Twenty bez těchto schopností.
</Note>
## API a front-end na samostatných serverech
Platí pouze v případě, že back-end neposkytuje front-end. Nasazení se stejným původem
zde nic nepotřebují.
Uveďte každý původ prohlížeče, který komunikuje s API, oddělený čárkami. `SERVER_URL`
a `FRONTEND_URL` jsou již důvěryhodné. Server zapisuje varování do logu, když jej prohlížeč
volá z nedeklarovaného původu.
```bash
AUTH_COOKIE_ALLOWED_ORIGINS=https://app.example.com
```
<Warning>
Front-end odesílá přihlašovací údaje u každého požadavku a prohlížeče odmítnou
odpověď s přihlašovacími údaji z nedeklarovaného původu, takže se aplikace nenačte, dokud toto nenastavíte.
</Warning>
Pokud jsou dva hostitelé na různých registrovatelných doménách (nejen na různých subdoménách), nastavte také `AUTH_COOKIE_SAME_SITE=none`, což vyžaduje HTTPS.
Je-li povoleno více pracovních prostorů (multi-workspace), neuvádějte zde každou subdoménu pracovního prostoru.
Místo toho směrujte cesty API (`/graphql`, `/metadata`, `/rest`, `/auth/`,
`/client-config`, `/file`) na back-end na každém hostname pracovního prostoru, aby každý
pracovní prostor zůstal ve stejném původu (same-origin) — takto běží Twenty Cloud.

View File

@@ -0,0 +1,255 @@
---
title: Řešení potíží
icon: wrench
---
## Řešení potíží
Pokud narazíte na jakýkoli problém při nastavování prostředí pro vývoj, při aktualizaci své instance nebo při samostatném hostování,
zde jsou některá řešení běžných problémů.
### Vlastní hosting
#### První instalace skončí chybou `password authentication failed for user "postgres"`
🚨 **DŮLEŽITÉ: Toto řešení je POUZE pro nové instalace** 🚨
Pokud máte existující instanci Twenty s produkčními daty, **NEPROVÁDĚJTE** tyto kroky, protože trvale smažou vaši databázi!
Při první instalaci Twenty můžete chtít změnit výchozí heslo pro databázi.
Heslo, které nastavíte během první instalace, se stane trvale uloženým v objemu databáze. Pokud se později pokusíte toto heslo změnit v konfiguraci, aniž byste odstranili starý objem, dojde k chybám autentizace, protože databáze stále používá původní heslo.
⚠️ VAROVÁNÍ: Následující kroky trvale SMAŽÍ veškerá data z databáze! ⚠️
Pokračujte pouze v případě, že se jedná o novou instalaci bez důležitých dat.
Chcete-li aktualizovat `PG_DATABASE_PASSWORD`, potřebujte:
```sh
# Update the PG_DATABASE_PASSWORD in .env
docker compose down --volumes
docker compose up -d
```
#### Nalezeny konce řádků CR [Windows]
K tomu dochází díky znakům s liniovým jádrem Windows v kombinaci s konfigurací Git. Zkuste spustit:
```
git config --global core.autocrlf false
```
Pak smažte úložiště a naklonujte jej znovu.
#### Chybí schéma metadat
Během instalace Twenty musíte nastavit svou postgres databázi s správnými schématy, rozšířeními a uživateli.
Pokud se vám podaří spustit toto nastavování, měli byste mít ve své databázi schémata `default` a `metadata`.
Pokud nemáte, ujistěte se, že na vašem počítači není spuštěna více než jedna PostgreSQL instance.
#### Nelze najít modul 'twenty-emails' ani jeho odpovídající deklarace typů.
Před inicializací databáze musíte vytvořit balíček `dvacet e-mailů` (`npx nx spustit dvacet e-mailů:build`).
#### Chybí balíček twenty-x
Ujistěte se, že v kořenovém adresáři spouštíte `yarn` a poté spusťte `npx nx server:dev twenty-server`. Pokud stále nefunguje, zkuste chybějící balíček sestavit ručně.
#### Lint při ukládání nefunguje
Toto by mělo fungovat přímo s nainstalovaným rozšířením Oxc (`oxc.oxc-vscode`). Pokud to nefunguje, zkuste to přidat do nastavení VS kódu (rozsah dev kontejneru):
```
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
```
#### Při běhu `npx nx start` nebo `npx nx start 21 front`, je vyhozena chyba mimo paměť
Spusťte pouze služby, které potřebujete, místo `npx nx start`. Například pokud pracujete na serveru, spusťte pouze `npx nx worker twenty-server`
**Pokud to nefunguje:**
Pokud jste se pokusili spustit pouze `npx nx run twenty-server:start` na WSL a selhává to s následující chybou paměti:
`FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`
Práce je spustit následující příkaz v terminálu, nebo ho přidat do profilu `.bashrc` tak, aby byl použit automaticky:
`export NODE_OPTIONS="--max-old-space-size=8192"`
Příznak --max-old-space-size=8192 nastavuje horní limit 8 GB pro hromadu v Node.js; využití se škáluje s požadavkem aplikace.
Odkaz: https://stackoverflow.com/questions/56982005/where-do-i-set-node-options-max-old-space-size-2048
**Pokud to nefunguje:**
Zjistit, které procesy přebírají většinu RAM. Ve dvaceti letech jsme si všimli, že některá rozšíření VS kódu zabírala spoustu RAM, takže jsme je dočasně zakázali.
**Pokud to nefunguje:**
Restartování zařízení pomáhá vyčistit ducha procesy.
#### Při běhu `npx nx start` se v protokolech objevují podivné [0] a [1]
To je očekávané, protože příkaz `npx nx start` spouští více příkazů v pozadí
#### E-maily nejsou odesílány
Většinou je to proto, že `worker` neběží na pozadí. Zkuste spustit
```
npx nx worker twenty-server
```
#### Nelze připojit můj účet Microsoft 365 k dvaceti
Většinou je to proto, že váš administrátor pro váš účet nepovolil licenci Microsoft 365. Zkontrolujte [https://admin.microsoft.com/](https://admin.microsoft.com/Adminportal/Home).
Pokud máte chybový kód `AADSTS50020`, pravděpodobně používáte osobní účet Microsoft. Tento přístup ještě není podporován. Více informací [zde](https://learn.microsoft.com/fr-fr/troubleshoot/entra/entra-id/app-integration/error-code-aadsts50020-user-account-identity-provider-does-not-exist)
#### Při běhu `yarn` se varování zobrazí v konzoli
Varování informují o načítání dalších závislostí, které nejsou explicitně uvedeny v `package.json`, takže pokud se neobjeví žádná vážná chyba, vše by mělo fungovat podle očekávání.
#### Když uživatel přistupuje na přihlašovací stránku, objeví se v záznamech chyba o neautorizovaném uživateli, který se snaží získat přístup k pracovnímu prostoru
To je očekáváno jako uživatel je neautorizovaný při odhlášení, protože jeho identita nebyla ověřena.
#### Jak zkontrolovat, zda váš worker běží?
* Jděte na [webhook-test.com](https://webhook-test.com/) a zkopírujte **Svou unikátní webhook URL**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-test.jpg" alt="Test webhooku" />
</div>
* Otevřete svou Twenty aplikaci, přejděte na `/settings` a na levé dolní části obrazovky aktivujte přepínač **Pokročilé**.
* Vytvořte nový webhook.
* Vložte **Svou unikátní webhook URL** do pole **Endpoint Url** ve Twenty. Nastavte **Filtry** na `Companies` a `Created`.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-settings.jpg" alt="Nastavení webhooku" />
</div>
* Přejděte na `/objects/companies` a vytvořte nový záznam společnosti.
* Vraťte se na [webhook-test.com](https://webhook-test.com/) a zkontrolujte, zda byla přijata nová **POST request**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/self-hosting/webhook-test-result.jpg" alt="Výsledek testu webhooku" />
</div>
* Pokud byla přijata **POST request**, váš worker běží úspěšně. V opačném případě je potřeba zkontrolovat váš worker.
#### Front-end se nespustí a chyba vrátí TS5042: Možnost 'project' nemůže být smíchána se zdrojovými soubory na příkazové řádce
Komentář kontrolního skriptu v `packages/twenty-ui/vite-config.ts`, jak je zobrazen v příkladu níže:
```
plugins: [
react({ jsxImportSource: 'react' }),
tsconfigPaths(),
svgr(),
dts(dtsConfig),
// checker(checkersConfig),
wyw({
include: [
'**/OverflowingTextWithTooltip.tsx',
'**/Chip.tsx',
'**/Tag.tsx',
'**/Avatar.tsx',
'**/AvatarChip.tsx',
],
babelOptions: {
presets: ['@babel/preset-typescript', '@babel/preset-react'],
},
}),
],
```
#### Administrační panel není přístupný
Spusťte `UPDATE core."user" SET "canAccessFullAdminPanel" = TRUE WHERE email = 'you@yourdomain.com';` v databázovém kontejneru, abyste získali přístup k administrátorskému panelu.
#### Při spuštění workflow dojde k chybě "Logic function execution is disabled. Set LOGIC_FUNCTION_TYPE to LOCAL or LAMBDA to enable."
V produkčním prostředí jsou logické funkce ve výchozím nastavení zakázané. Nastavte proměnnou prostředí `LOGIC_FUNCTION_TYPE` na `LOCAL` nebo `LAMBDA`, abyste je povolili. Toto lze nakonfigurovat pomocí proměnných prostředí nebo prostřednictvím proměnných databáze v administračním panelu. Podrobnosti najdete v [průvodci nastavením Logic Functions](/l/cs/developers/self-host/capabilities/setup#logic-functions-available-drivers).
#### Když napíšu zprávu v AI chatu, neodpoví okamžitě a musím aktualizovat stránku, abych viděl odpověď
To znamená, že vaší reverzní proxy chybí konfigurace pro streamované API odpovědi. Níže je uveden příklad konfigurace Nginx:
```
location ~* ^/api/.*stream|\/graphql\/stream {
proxy_pass http://twenty_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
gzip off;
#chunked_transfer_encoding off;
add_header X-Accel-Buffering no always;
}
```
### Sestavení Dockeru
#### Nelze se přihlásit
Pokud se nemůžete přihlásit po nastavení:
1. Spusťte následující příkazy:
```bash
docker exec -it twenty-server-1 yarn
docker exec -it twenty-server-1 npx nx database:reset --configuration=no-seed
```
2. Restartujte Docker kontejnery:
```bash
docker compose down
docker compose up -d
```
Všimněte si, že příkaz `database:reset` zcela vymaže databázi a znovu ji obnoví od nuly.
#### Problémy s připojením za reverzní proxy
Pokud provozujete Twenty za reverzní proxy a máte problémy s připojením:
1. **Ověřte SERVER_URL:**
Ujistěte se, že `SERVER_URL` ve vašem `.env` souboru odpovídá vaší externí přístupové URL, včetně `https`, pokud je SSL povoleno.
2. **Zkontrolujte nastavení reverzní proxy:**
* Potvrďte, že vaše reverzní proxy správně směruje požadavky na server Twenty.
* Ujistěte se, že záhlaví jako `X-Forwarded-For` a `X-Forwarded-Proto` jsou správně nastaveny.
3. **Restartujte služby:**
Po provedení změn restartujte jak reverzní proxy, tak kontejnery Twenty.
#### Chyba při nahrávání obrázku - přístup odepřen
Změna vlastnictví datové složky na hostiteli z uživatele root na jiného uživatele a skupinu tento problém vyřeší.
#### Chyba „Soubor nenalezen“ při testování funkce Logic v pracovním postupu
Spusťte příkaz `yarn command:prod application:rebuild-default-deps` ve svém kontejneru twenty-server, abyste opravili problém s chybějícími závislostmi.
## Získání pomoci
Pokud se setkáte s problémy, které tento průvodce nepokrývá:
* Zkontrolujte protokoly:
Zobrazte protokoly kontejneru kvůli chybovým zprávám:
```bash
docker compose logs
```
* Podpora komunity:
Obraťte se na [komunitu Twenty](https://github.com/twentyhq/twenty/issues) nebo [podpůrné kanály](https://discord.gg/cx5n4Jzs57) pro pomoc.

View File

@@ -0,0 +1,106 @@
---
title: Průvodce upgradem
icon: arrow-up-right-dots
---
## Obecné pokyny
**Před zahájením procesu aktualizace vždy zálohujte svou databázi** spuštěním:
```bash
docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql
```
Pro obnovení ze zálohy:
```bash
cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}
```
Pokud používáte Docker Compose, postupujte takto:
1. Zastavte Twenty: `docker compose down`
2. Změňte hodnotu `TAG` v souboru `.env` vedle `docker-compose.yml`
3. Spusťte Twenty: `docker compose up -d`
Server při startu automaticky spustí všechny požadované migrační kroky upgradu. Není potřeba žádný ruční příkaz.
## Upgrade na v2.34+ — vyžaduje minimálně PostgreSQL 15
Twenty v2.34 vyžaduje PostgreSQL 15 nebo novější. Oficiální konfigurace Docker Compose používá PostgreSQL 16. Pokud se vaše instance připojuje k externě spravované databázi, upgradujte ji před spuštěním serveru v2.34.
## Upgrade napříč verzemi (v1.23+)
Počínaje **v1.23** podporuje Twenty upgrade napříč verzemi. Můžete přejít přímo z jakékoli podporované verze na nejnovější vydání, aniž byste museli procházet každou meziverzí.
Například přechod z v1.23 přímo na v2.0 je plně podporován.
## Aktualizace na v2.5+ — obálka pro šifrování dat v klidu
Od **v2.5** ukládá Twenty tajné údaje v klidu (OAuth tokeny, aplikační proměnné, privátní klíče pro podepisování, citlivé konfigurační hodnoty, TOTP tajemství) do verzované obálky `enc:v2:`, která je šifrovaná pomocí `ENCRYPTION_KEY` (nebo `APP_SECRET`, pokud není `ENCRYPTION_KEY` nastavený).
Při prvním startu na v2.5 se spustí pomalé aktualizační příkazy, které **zpětně doplní** stávající řádky do nové obálky. Jsou idempotentní — při přerušení a opětovném spuštění server pokračuje tam, kde skončil — ale u velkých databází to může chvíli trvat. Postup můžete sledovat pomocí `upgrade:status`.
Měli byste nastavit vyhrazený `ENCRYPTION_KEY` **před** upgradem na v2.5, aby zpětné doplňování od začátku zapisovalo řádky pod tímto klíčem. Změna klíčů po dokončení doplnění vyžaduje [rotaci](/l/cs/developers/self-host/capabilities/key-rotation).
## Rotace tajemství a podepisovacích klíčů
Pro běžné provozní úkony, jako je rotace `ENCRYPTION_KEY`, rotace podepisovacího klíče JWT nebo zneplatnění uniklého podepisovacího klíče, viz samostatný [průvodce rotací klíčů](/l/cs/developers/self-host/capabilities/key-rotation).
## Kontrola stavu upgradu
Příkaz `upgrade:status` vám umožní zkontrolovat aktuální stav vaší instance a migrací pracovních prostorů. Je užitečný pro ladění problémů s upgradem nebo při podávání požadavku na podporu.
Spusťte jej z kontejneru serveru:
```bash
docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status
```
Ukázkový výstup:
```sh
APP_VERSION: v1.23.0
Instance
Inferred version: 1.23.0
Latest command: 1.23.0_DropWorkspaceVersionColumnFastInstanceCommand_1785000000000
Status: Up to date
Executed by: v1.23.0
At: 2026-04-16T11:43:58.823Z
Workspace
Apple (20202020-1c25-4d02-bf25-6aeccf7ea419)
Inferred version: 1.23.0
Latest command: 1.23.0_UpdateGlobalObjectContextCommandMenuItemsCommand_1780000005000
Status: Up to date
Executed by: v1.23.0
At: 2026-04-16T11:44:09.361Z
Summary
Instance: Up to date
Workspaces: 1 up to date, 0 behind, 0 failed (1 total)
```
### Možnosti
| Přepínač | Popis |
| ------------------------- | ------------------------------------------------------------------------------------ |
| `-w, --workspace-id <id>` | Omezí se na konkrétní pracovní prostor. Lze zadat vícekrát. |
| `-f, --failed-only` | Skryje aktuální pracovní prostory; zobrazí pouze ty, které jsou pozadu, a neúspěšné. |
## Řešení potíží
Pokud upgrade selže u některých pracovních prostorů, server nepokročí za chybující krok. Restartování serveru (`docker compose up -d`) zkusí upgrade znovu od místa, kde se zastavil.
Pro rychlou identifikaci problémů spusťte:
```bash
docker exec -it {server_container_name_or_id} yarn command:prod upgrade:status --failed-only
```
Tímto se zobrazí pouze pracovní prostory, které jsou pozadu nebo selhaly, spolu s chybovou zprávou pro každé selhání.
## Před v1.23
Pokud je vaše instance starší než v1.23, musíte upgradovat postupně přes každou hlavní označenou verzi (z v1.6 na v1.7, poté z v1.7 na v1.8 atd.), dokud nedosáhnete v1.23. Odtud můžete přejít přímo na nejnovější verzi.

View File

@@ -0,0 +1,26 @@
---
title: Vlastní hosting
description: Nasaďte a spravujte Twenty na vlastní infrastruktuře.
---
<Frame>
<img src="/images/user-guide/what-is-twenty/20.png" alt="AI" />
</Frame>
## Přehled
Twenty můžete samostatně hostovat na vlastní infrastruktuře, což vám dává plnou kontrolu nad daty a nasazením.
## Proč samohostovat?
* **Vlastnictví dat**: Uchovávejte veškerá data CRM na vlastních serverech
* **Soulad**: Splňte regulační požadavky na umístění dat
* **Přizpůsobení**: Plný přístup k úpravám a rozšiřování platformy
## Začínáme
<CardGroup cols={2}>
<Card title="Docker Compose" icon="docker" href="/l/cs/developers/self-host/capabilities/docker-compose">
Rychlé nastavení s Dockerem
</Card>
</CardGroup>