i18n - docs translations (#19925)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
committed by
GitHub
parent
15938c1fca
commit
cd73088be6
+1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Příkazy backendu
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## Užitečné příkazy
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
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.
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Architektura složek
|
||||
icon: folder-tree
|
||||
info: Podrobný pohled na naši architekturu složek
|
||||
---
|
||||
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Příkazy Frontend
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## Užitečné příkazy
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Stylová příručka
|
||||
icon: paintbrush
|
||||
---
|
||||
|
||||
Tento dokument obsahuje pravidla pro psaní kódu.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
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ě.
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Commands
|
||||
icon: terminal
|
||||
description: Useful commands for developing Twenty.
|
||||
---
|
||||
|
||||
Commands can be run from the repository root using `npx nx`. Use `npx nx run {project}:{command}` for explicit targeting.
|
||||
|
||||
## Starting the App
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Database
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Type Checking
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
npx nx build twenty-shared # Must be built first
|
||||
npx nx build twenty-front
|
||||
npx nx build twenty-server
|
||||
```
|
||||
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: Stylová příručka
|
||||
icon: paintbrush
|
||||
description: Code conventions and best practices for contributing to Twenty.
|
||||
---
|
||||
|
||||
## React
|
||||
|
||||
### Functional components only
|
||||
|
||||
Always use TSX functional components with named exports.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const MyComponent = () => {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
export default MyComponent;
|
||||
|
||||
// ✅ Good
|
||||
export function MyComponent() {
|
||||
return <div>Hello World</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Vlastnosti
|
||||
|
||||
Create a type named `{ComponentName}Props`. Use destructuring. Don't use `React.FC`.
|
||||
|
||||
```tsx
|
||||
type MyComponentProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const MyComponent = ({ name }: MyComponentProps) => <div>Hello {name}</div>;
|
||||
```
|
||||
|
||||
### No single-variable prop spreading
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
const MyComponent = (props: MyComponentProps) => <Other {...props} />;
|
||||
|
||||
// ✅ Good
|
||||
const MyComponent = ({ prop1, prop2 }: MyComponentProps) => <Other {...{ prop1, prop2 }} />;
|
||||
```
|
||||
|
||||
## Správa stavu
|
||||
|
||||
### Jotai atoms for global state
|
||||
|
||||
```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',
|
||||
});
|
||||
```
|
||||
|
||||
* Prefer atoms over prop drilling
|
||||
* Don't use `useRef` for state — use `useState` or atoms
|
||||
* Use atom families and selectors for lists
|
||||
|
||||
### Avoid unnecessary re-renders
|
||||
|
||||
* Extract `useEffect` and data fetching into sibling sidecar components
|
||||
* Prefer event handlers (`handleClick`, `handleChange`) over `useEffect`
|
||||
* Don't use `React.memo()` — fix the root cause instead
|
||||
* Limit `useCallback` / `useMemo` usage
|
||||
|
||||
```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` over `interface`** — more flexible, easier to compose
|
||||
* **String literals over enums** — except for GraphQL codegen enums and internal library APIs
|
||||
* **No `any`** — strict TypeScript enforced
|
||||
* **No type imports** — use regular imports (enforced by Oxlint `typescript/consistent-type-imports`)
|
||||
* **Use [Zod](https://github.com/colinhacks/zod)** for runtime validation of untyped objects
|
||||
|
||||
## JavaScript
|
||||
|
||||
```tsx
|
||||
// Use nullish-coalescing (??) instead of ||
|
||||
const value = process.env.MY_VALUE ?? 'default';
|
||||
|
||||
// Use optional chaining
|
||||
onClick?.();
|
||||
```
|
||||
|
||||
## Pojmenovávání
|
||||
|
||||
* **Variables**: camelCase, descriptive (`email` not `value`, `fieldMetadata` not `fm`)
|
||||
* **Constants**: SCREAMING_SNAKE_CASE
|
||||
* **Types/Classes**: PascalCase
|
||||
* **Files/directories**: kebab-case (`.component.tsx`, `.service.ts`, `.entity.ts`)
|
||||
* **Event handlers**: `handleClick` (not `onClick` for the handler function)
|
||||
* **Component props**: prefix with component name (`ButtonProps`)
|
||||
* **Styled components**: prefix with `Styled` (`StyledTitle`)
|
||||
|
||||
## Styling
|
||||
|
||||
Use [Linaria](https://github.com/callstack/linaria) styled components. Use theme values — avoid hardcoded `px`, `rem`, or colors.
|
||||
|
||||
```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
|
||||
|
||||
Use aliases instead of relative paths:
|
||||
|
||||
```tsx
|
||||
// ❌ Bad
|
||||
import { Foo } from '../../../../../testing/decorators/Foo';
|
||||
|
||||
// ✅ Good
|
||||
import { Foo } from '~/testing/decorators/Foo';
|
||||
import { Bar } from '@/modules/bar/components/Bar';
|
||||
```
|
||||
|
||||
## Folder Structure
|
||||
|
||||
```
|
||||
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, ...)
|
||||
```
|
||||
|
||||
* Modules can import from other modules, but `ui/` should stay dependency-free
|
||||
* Use `internal/` subfolders for module-private code
|
||||
* Components under 300 lines, services under 500 lines
|
||||
@@ -1,147 +1,55 @@
|
||||
---
|
||||
title: API
|
||||
description: Programově dotazujte a upravujte svá CRM data pomocí REST nebo GraphQL.
|
||||
icon: plug
|
||||
description: REST and GraphQL APIs generated from your workspace schema.
|
||||
---
|
||||
|
||||
import { VimeoEmbed } from '/snippets/vimeo-embed.mdx';
|
||||
|
||||
Twenty bylo vytvořeno s ohledem na vývojáře a nabízí 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.
|
||||
## Schema-per-tenant APIs
|
||||
|
||||
## Přístup orientovaný na vývojáře
|
||||
There is no static API reference for Twenty. Each workspace has its own schema — when you add a custom object (say `Invoice`), it immediately gets REST and GraphQL endpoints identical to built-in objects like `Company` or `Person`. The API is generated from the schema, so endpoints use your object and field names directly — no opaque IDs.
|
||||
|
||||
Twenty generuje API specificky pro váš datový model:
|
||||
Your workspace-specific API documentation is available under **Settings → API & Webhooks** after creating an API key. It includes an interactive playground where you can execute real calls against your data.
|
||||
|
||||
* **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 každé pole má svůj vlastní koncový bod API.
|
||||
* **Vlastní dokumentace**: Generována specificky pro datový model vašeho pracovního prostoru.
|
||||
## Two APIs
|
||||
|
||||
<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>
|
||||
**Core API** — `/rest/` and `/graphql/`
|
||||
|
||||
## Dva typy API
|
||||
CRUD on records: People, Companies, Opportunities, your custom objects. Query, filter, traverse relations.
|
||||
|
||||
### Core API
|
||||
**Metadata API** — `/rest/metadata/` and `/metadata/`
|
||||
|
||||
Přístupné na `/rest/` nebo `/graphql/`
|
||||
Schema management: create/modify/delete objects, fields, and relations. This is how you programmatically change your data model.
|
||||
|
||||
Pracujte se svými skutečnými **záznamy** (daty):
|
||||
Both are available as REST and GraphQL. GraphQL adds batch upserts and the ability to traverse relations in a single query. Same underlying data either way.
|
||||
|
||||
* Vytvářejte, čtěte, aktualizujte a mazejte osoby, společnosti, příležitosti atd.
|
||||
* Dotazujte a filtrujte data
|
||||
* Spravujte vztahy mezi záznamy.
|
||||
## Base URLs
|
||||
|
||||
### Metadata API
|
||||
|
||||
Přístupné na `/rest/metadata/` nebo `/metadata/`
|
||||
|
||||
Spravujte svůj **pracovní prostor a datový model**:
|
||||
|
||||
* Vytvářejte, upravujte nebo mazejte objekty a pole.
|
||||
* Konfigurujte 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}/` |
|
||||
| Prostředí | Základní URL |
|
||||
| ----------- | ------------------------- |
|
||||
| Cloud | `https://api.twenty.com/` |
|
||||
| Self-Hosted | `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
|
||||
Create an API key in **Settings → API & Webhooks → + Create key**. Copy it immediately — it's shown once. Keys can be scoped to a specific role under **Settings → Roles → Assignment tab** to limit what they can access.
|
||||
|
||||
<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>
|
||||
For OAuth-based access (external apps acting on behalf of users), see [OAuth](/l/cs/developers/extend/oauth).
|
||||
|
||||
### Přiřaďte roli klíči API
|
||||
## Batch operations
|
||||
|
||||
Pro vyšší bezpečnost přiřaďte konkrétní roli, abyste omezili přístup:
|
||||
Both REST and GraphQL support batching up to 60 records per request — create, update, or delete. GraphQL also supports batch upsert (create-or-update in one call) using plural names like `CreateCompanies`.
|
||||
|
||||
1. Přejděte na **Nastavení → 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
|
||||
## Rate limits
|
||||
|
||||
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>
|
||||
| Limit | Hodnota |
|
||||
| ---------- | -------------------- |
|
||||
| Requests | 100 per minute |
|
||||
| Batch size | 60 záznamů na volání |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,434 @@
|
||||
---
|
||||
title: CLI & Testing
|
||||
description: CLI commands, testing setup, public assets, npm packages, remotes, and CI configuration.
|
||||
icon: terminal
|
||||
---
|
||||
|
||||
## Veřejné prostředky (složka `public/`)
|
||||
|
||||
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 `logoUrl` a `screenshots` v `defineApplication()` odkazují na soubory z této složky (např. `public/logo.png`). Tyto se zobrazují v Marketplace, když je vaše aplikace zveřejněna.
|
||||
* **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 restart.
|
||||
* **Zahrnuté do buildů** — `yarn twenty 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, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
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, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
export default defineFrontComponent(() => {
|
||||
const logoUrl = getPublicAssetUrl('logo.png');
|
||||
|
||||
return <img src={logoUrl} alt="App logo" />;
|
||||
});
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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ží všechny 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. Všechny importované balíčky jsou vloženy přímo do bundlu.
|
||||
|
||||
**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 prohlížečová API a balíčky npm, které fungují v prohlížečovém prostředí.
|
||||
|
||||
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á.
|
||||
|
||||
## Testování vaší aplikace
|
||||
|
||||
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.
|
||||
|
||||
### 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';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
projects: ['tsconfig.spec.json'],
|
||||
ignoreConfigErrors: true,
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
testTimeout: 120_000,
|
||||
hookTimeout: 120_000,
|
||||
include: ['src/**/*.integration-test.ts'],
|
||||
setupFiles: ['src/__tests__/setup-test.ts'],
|
||||
env: {
|
||||
TWENTY_API_URL: 'http://localhost:2020',
|
||||
TWENTY_API_KEY: 'your-api-key',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Vytvořte soubor nastavení, který před spuštěním testů ověří dostupnost serveru:
|
||||
|
||||
```ts src/__tests__/setup-test.ts
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
|
||||
beforeAll(async () => {
|
||||
// Verify the server is running
|
||||
const response = await fetch(`${TWENTY_API_URL}/healthz`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Twenty server is not reachable at ${TWENTY_API_URL}. ` +
|
||||
'Start the server before running integration tests.',
|
||||
);
|
||||
}
|
||||
|
||||
// Write a temporary config for the SDK
|
||||
fs.mkdirSync(TEST_CONFIG_DIR, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(TEST_CONFIG_DIR, 'config.json'),
|
||||
JSON.stringify({
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
}, null, 2),
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### 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 |
|
||||
| `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 typecheck
|
||||
```
|
||||
|
||||
Spustí se `tsc --noEmit` a nahlásí se případné chyby typů.
|
||||
|
||||
## Referenční dokumentace CLI
|
||||
|
||||
Kromě `dev`, `build`, `add` a `typecheck` poskytuje CLI příkazy pro spouštění funkcí, zobrazení logů a správu instalací aplikací.
|
||||
|
||||
### Spouštění funkcí (`yarn twenty 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 exec -n create-new-post-card
|
||||
|
||||
# Execute by universalIdentifier
|
||||
yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
|
||||
# Pass a JSON payload
|
||||
yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}'
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
### Zobrazení logů funkcí (`yarn twenty logs`)
|
||||
|
||||
Streamujte výstupní logy běhu logických funkcí vaší aplikace:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Stream all function logs
|
||||
yarn twenty logs
|
||||
|
||||
# Filter by function name
|
||||
yarn twenty logs -n create-new-post-card
|
||||
|
||||
# Filter by universalIdentifier
|
||||
yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
<Note>
|
||||
To je jiné než `yarn twenty server logs`, které zobrazují logy kontejneru Docker. `yarn twenty logs` zobrazuje logy spuštění funkcí vaší aplikace ze serveru Twenty.
|
||||
</Note>
|
||||
|
||||
### Odinstalace aplikace (`yarn twenty uninstall`)
|
||||
|
||||
Odeberte svou aplikaci z aktivního pracovního prostoru:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty uninstall
|
||||
|
||||
# Skip the confirmation prompt
|
||||
yarn twenty 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 --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
|
||||
|
||||
# List all configured remotes
|
||||
yarn twenty remote list
|
||||
|
||||
# Switch the active remote
|
||||
yarn twenty remote switch <name>
|
||||
```
|
||||
|
||||
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.
|
||||
|
||||
## CI s GitHub Actions
|
||||
|
||||
Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů.
|
||||
|
||||
Workflow:
|
||||
|
||||
1. Načte váš kód (checkout).
|
||||
2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
|
||||
3. Nainstaluje závislosti pomocí `yarn install --immutable`
|
||||
4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce
|
||||
|
||||
```yaml .github/workflows/ci.yml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
TWENTY_VERSION: latest
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Spawn Twenty instance
|
||||
id: twenty
|
||||
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
|
||||
with:
|
||||
twenty-version: ${{ env.TWENTY_VERSION }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Enable Corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run integration tests
|
||||
run: yarn test
|
||||
env:
|
||||
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
||||
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
|
||||
```
|
||||
|
||||
Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí efemérní server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky.
|
||||
|
||||
Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow.
|
||||
@@ -0,0 +1,494 @@
|
||||
---
|
||||
title: Datový model
|
||||
description: Define objects, fields, roles, and application metadata with the Twenty SDK.
|
||||
icon: database
|
||||
---
|
||||
|
||||
The `twenty-sdk` package provides `defineEntity` functions to declare your app's data model. Abyste umožnili SDK detekovat vaše entity, musíte použít `export default defineEntity({...})`. Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
|
||||
|
||||
<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í. Seskupování souborů podle typu (např. `logic-functions/`, `roles/`) je pouze konvence, nikoli požadavek.
|
||||
</Note>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineRole" description="Konfigurace oprávnění rolí a přístupu k objektům">
|
||||
|
||||
Role zapouzdřují oprávnění k objektům a akcím ve vašem pracovním prostoru.
|
||||
|
||||
```ts restricted-company-role.ts
|
||||
import {
|
||||
defineRole,
|
||||
PermissionFlag,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} 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,
|
||||
},
|
||||
],
|
||||
permissionFlags: [PermissionFlag.APPLICATIONS],
|
||||
});
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineApplication" description="Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci)">
|
||||
|
||||
Každá aplikace musí mít právě jedno volání `defineApplication`, které popisuje:
|
||||
|
||||
* **Identita**: identifikátory, zobrazovaný název a popis.
|
||||
* **Oprávnění**: jakou roli používají její funkce a frontendové komponenty.
|
||||
* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí.
|
||||
* **(Volitelné) předinstalační / postinstalační funkce**: logické funkce, které se spouštějí před nebo po instalaci.
|
||||
|
||||
```ts src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '39783023-bcac-41e3-b0d2-ff1944d8465d',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
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 (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` musí odkazovat na roli definovanou pomocí `defineRole()` (viz výše).
|
||||
* Předinstalační a postinstalační funkce jsou při sestavení manifestu detekovány automaticky — není třeba na ně odkazovat v `defineApplication()`.
|
||||
|
||||
#### Metadata tržiště
|
||||
|
||||
Pokud plánujete [zveřejnit svou aplikaci](/l/cs/developers/extend/apps/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 |
|
||||
| `logoUrl` | Cesta k logu vaší aplikace (např. `public/logo.png`) |
|
||||
| `screenshots` | Pole cest ke snímkům obrazovky (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ů |
|
||||
|
||||
#### Role a oprávnění
|
||||
|
||||
Pole `defaultRoleUniversalIdentifier` v `application-config.ts` určuje výchozí roli používanou logickými funkcemi a frontendovými komponentami vaší aplikace. Podrobnosti viz výše u `defineRole`.
|
||||
|
||||
* Běhový token vložený jako `TWENTY_APP_ACCESS_TOKEN` je odvozen z této role.
|
||||
* Typovaný klient bude omezen oprávněními udělenými této roli.
|
||||
* Dodržujte princip nejmenších oprávnění: vytvořte vyhrazenou roli pouze s oprávněními, která vaše funkce potřebují.
|
||||
|
||||
##### Výchozí role funkce
|
||||
|
||||
Když vygenerujete novou aplikaci, CLI vytvoří výchozí soubor role:
|
||||
|
||||
```ts src/roles/default-role.ts
|
||||
import { defineRole, PermissionFlag } from 'twenty-sdk/define';
|
||||
|
||||
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER =
|
||||
'b648f87b-1d26-4961-b974-0908fd991061';
|
||||
|
||||
export default defineRole({
|
||||
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: [],
|
||||
permissionFlags: [],
|
||||
});
|
||||
```
|
||||
|
||||
Na `universalIdentifier` této role se v `application-config.ts` odkazuje jako na `defaultRoleUniversalIdentifier`:
|
||||
|
||||
* **\*.role.ts** definuje, co daná role může dělat.
|
||||
* **application-config.ts** ukazuje na tuto roli, aby vaše funkce zdědily její oprávnění.
|
||||
|
||||
Poznámky:
|
||||
* Začněte vygenerovanou rolí a postupně ji omezujte podle principu nejmenších oprávnění.
|
||||
* Nahraďte `objectPermissions` a `fieldPermissions` objekty a poli, které vaše funkce skutečně potřebují.
|
||||
* `permissionFlags` řídí přístup k schopnostem na úrovni platformy. Udržujte je co nejmenší.
|
||||
* Podívejte se na funkční příklad: [`hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineObject" description="Definice vlastních objektů s poli">
|
||||
|
||||
Vlastní objekty popisují jak schéma, tak chování záznamů ve vašem pracovním prostoru. K definování objektů s vestavěnou validací použijte `defineObject()`:
|
||||
|
||||
```ts postCard.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:
|
||||
|
||||
* Použijte `defineObject()` pro vestavěnou validaci a lepší podporu v IDE.
|
||||
* 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í.
|
||||
* Nové objekty můžete vygenerovat pomocí `yarn twenty add`, který vás provede pojmenováním, poli a vztahy.
|
||||
|
||||
<Note>
|
||||
**Základní pole jsou vytvořena automaticky.** Když definujete vlastní objekt, Twenty automaticky přidá standardní pole
|
||||
jako `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` a `deletedAt`.
|
||||
Nemusíte je definovat v poli `fields` — přidejte pouze svá vlastní pole.
|
||||
Výchozí pole můžete přepsat definováním pole se stejným názvem v poli `fields`,
|
||||
ale to se nedoporučuje.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineField — Standardní pole" description="Rozšiřte existující objekty o další pole">
|
||||
|
||||
Pomocí `defineField()` přidejte pole k objektům, které nevlastníte — například ke standardním objektům Twenty (Person, Company atd.). nebo k objektům z jiných aplikací. Na rozdíl od inline polí v `defineObject()` 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 použijte `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` exportovaný z `twenty-sdk`.
|
||||
* Při definování polí inline v `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()`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineField — Relační pole" description="Propojte objekty obousměrnými relacemi">
|
||||
|
||||
Relace propojují objekty. Ve Twenty jsou relace vždy obousměrné — definujete obě strany a každá strana odkazuje na tu druhou.
|
||||
|
||||
Existují dva typy relací:
|
||||
|
||||
| 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ů
|
||||
|
||||
Předpokládejme, že `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 souboru je importujte. Build systém je vyřeší v době kompilace.
|
||||
</Note>
|
||||
|
||||
#### 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`) |
|
||||
|
||||
#### Vložená relační pole v defineObject
|
||||
|
||||
Relační pole můžete také definovat přímo uvnitř `defineObject()`. V takovém případě 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
|
||||
],
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Generování entit pomocí `yarn twenty add`
|
||||
|
||||
Místo ručního vytváření souborů entit můžete použít interaktivní generátor:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty add
|
||||
```
|
||||
|
||||
Požádá vás o výběr typu entity a provede vás požadovanými poli. Vygeneruje soubor připravený k použití se stabilní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 add object
|
||||
yarn twenty add logicFunction
|
||||
yarn twenty add frontComponent
|
||||
```
|
||||
|
||||
### Dostupné typy entit
|
||||
|
||||
| Typ entity | Příkaz | Vygenerovaný soubor |
|
||||
| ------------------------- | ------------------------------------ | ------------------------------------------------------- |
|
||||
| Objekt | `yarn twenty add object` | `src/objects/\<name>.ts` |
|
||||
| Pole | `yarn twenty add field` | `src/fields/\<name>.ts` |
|
||||
| Logická funkce | `yarn twenty add logicFunction` | `src/logic-functions/\<name>.ts` |
|
||||
| Frontendová komponenta | `yarn twenty add frontComponent` | `src/front-components/\<name>.tsx` |
|
||||
| Role | `yarn twenty add role` | `src/roles/\<name>.ts` |
|
||||
| Dovednost | `yarn twenty add skill` | `src/skills/\<name>.ts` |
|
||||
| Agent | `yarn twenty add agent` | `src/agents/\<name>.ts` |
|
||||
| Pohled | `yarn twenty add view` | `src/views/\<name>.ts` |
|
||||
| Položka navigační nabídky | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/\<name>.ts` |
|
||||
| Rozvržení stránky | `yarn twenty add pageLayout` | `src/page-layouts/\<name>.ts` |
|
||||
|
||||
### Co generátor vytváří
|
||||
|
||||
Každý typ entity má vlastní šablonu. Například `yarn twenty 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 add logicFunction --path src/custom-folder
|
||||
```
|
||||
@@ -0,0 +1,419 @@
|
||||
---
|
||||
title: Frontendové komponenty
|
||||
description: Build React components that render inside Twenty's UI with sandboxed isolation.
|
||||
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 je sandboxovaný, ale vykresluje se nativně na stránce, nikoli v iframu.
|
||||
|
||||
## Kde lze použít frontendové komponenty
|
||||
|
||||
Frontendové komponenty se mohou vykreslovat na dvou 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ů)** — Frontendové komponenty lze vkládat jako widgety do rozložení stránek. Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget frontendové komponenty.
|
||||
|
||||
## Základní příklad
|
||||
|
||||
Nejrychlejší způsob, jak vidět frontendovou komponentu v akci, je zaregistrovat ji jako **příkaz**. Přidáním pole `command` s `isPinned: true` se zobrazí jako tlačítko rychlé akce v pravém horním rohu stránky — není potřeba žádné rozvržení 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,
|
||||
command: {
|
||||
universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-456789012345',
|
||||
shortLabel: 'Hello',
|
||||
label: 'Hello World',
|
||||
icon: 'IconBolt',
|
||||
isPinned: true,
|
||||
availabilityType: 'GLOBAL',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Po synchronizaci pomocí `yarn twenty dev` (nebo po jednorázovém spuštění `yarn twenty dev --once`) 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) |
|
||||
| `command` | Ne | Zaregistrujte komponentu jako příkaz (viz [možnosti příkazu](#command-options) 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 sekce [definePageLayout](/l/cs/developers/extend/apps/skills-and-agents#definepagelayout).
|
||||
|
||||
## 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 { useRecordId, enqueueSnackbar } from 'twenty-sdk/front-component';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const SyncTracker = () => {
|
||||
const recordId = useRecordId();
|
||||
|
||||
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/command`:
|
||||
|
||||
* **`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 konkrétní stránku postranního panelu. Props: `page`, `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/command';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
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,
|
||||
command: {
|
||||
universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345',
|
||||
label: 'Run my action',
|
||||
icon: 'IconPlayerPlay',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
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/command';
|
||||
|
||||
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,
|
||||
command: {
|
||||
universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567',
|
||||
label: 'Delete draft',
|
||||
icon: 'IconTrash',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 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,
|
||||
useRecordId,
|
||||
useFrontComponentId,
|
||||
} from 'twenty-sdk/front-component';
|
||||
|
||||
const RecordInfo = () => {
|
||||
const userId = useUserId();
|
||||
const recordId = useRecordId();
|
||||
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 |
|
||||
| `useRecordId()` | `string` nebo `null` | ID aktuálního záznamu (pokud je umístěna na stránce záznamu) |
|
||||
| `useFrontComponentId()` | `string` | ID této instance komponenty |
|
||||
| `useFrontComponentExecutionContext(selector)` | různé | Přístup k úplnému kontextu běhu pomocí selektorové funkce |
|
||||
|
||||
## 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 |
|
||||
|
||||
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 { useRecordId } from 'twenty-sdk/front-component';
|
||||
import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk/front-component';
|
||||
import { CoreApiClient } from 'twenty-sdk/clients';
|
||||
|
||||
const ArchiveRecord = () => {
|
||||
const recordId = useRecordId();
|
||||
|
||||
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,
|
||||
});
|
||||
```
|
||||
|
||||
## Možnosti příkazu
|
||||
|
||||
Přidání pole `command` do `defineFrontComponent` zaregistruje komponentu v příkazovém menu (Cmd+K). Pokud je `isPinned` nastaveno na `true`, zobrazí se také jako tlačítko rychlé akce v pravém horním rohu stránky.
|
||||
|
||||
| Pole | Povinné | Popis |
|
||||
| --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `universalIdentifier` | Ano | Stabilní jedinečné ID pro příkaz |
|
||||
| `label` | Ano | Plný popisek zobrazený v příkazovém menu (Cmd+K) |
|
||||
| `shortLabel` | Ne | Kratší popisek zobrazený na připnutém tlačítku rychlé akce |
|
||||
| `icon` | Ne | Název ikony zobrazený vedle popisku (např. `'IconBolt'`, `'IconSend'`) |
|
||||
| `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é), `'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 pro dynamické řízení, zda je příkaz viditelný (viz níže) |
|
||||
|
||||
## 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`:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
pageType,
|
||||
numberOfSelectedRecords,
|
||||
objectPermissions,
|
||||
everyEquals,
|
||||
isDefined,
|
||||
} from 'twenty-sdk/front-component';
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: '...',
|
||||
name: 'bulk-action',
|
||||
component: BulkAction,
|
||||
command: {
|
||||
universalIdentifier: '...',
|
||||
label: 'Bulk Update',
|
||||
availabilityType: 'RECORD_SELECTION',
|
||||
conditionalAvailabilityExpression: everyEquals(
|
||||
objectPermissions,
|
||||
'canUpdateObjectRecords',
|
||||
true,
|
||||
),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Kontextové 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** — kombinují 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ý řetězec |
|
||||
| `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 |
|
||||
|
||||
## Veřejné soubory
|
||||
|
||||
Frontendové komponenty mohou přistupovat k souborům ze složky aplikace `public/` pomocí `getPublicAssetUrl`:
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent, getPublicAssetUrl } from 'twenty-sdk/define';
|
||||
|
||||
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/cli-and-testing#public-assets-public-folder).
|
||||
|
||||
## Styling
|
||||
|
||||
Frontendové komponenty podporují více přístupů ke stylování. Můžete použít:
|
||||
|
||||
* **Inline styly** — `style={{ color: 'red' }}`
|
||||
* **Komponenty Twenty UI** — import z `twenty-sdk/ui` (Button, Tag, Status, Chip, Avatar a další)
|
||||
* **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
|
||||
|
||||
```tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { Button, Tag, Status } from 'twenty-sdk/ui';
|
||||
|
||||
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,
|
||||
});
|
||||
```
|
||||
@@ -1,12 +1,9 @@
|
||||
---
|
||||
title: Začínáme
|
||||
icon: rocket
|
||||
description: Vytvořte svou první aplikaci Twenty během několika minut.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
## Co jsou aplikace?
|
||||
|
||||
Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, frontendové komponenty, AI schopnosti a další — vše je spravováno jako kód. Místo konfigurace všeho přes uživatelské rozhraní definujete v TypeScriptu svůj datový model a logiku a nasadíte je do jednoho nebo více pracovních prostorů.
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: Rozvržení
|
||||
description: Define views, navigation menu items, and page layouts to shape how your app appears in Twenty.
|
||||
icon: table-columns
|
||||
---
|
||||
|
||||
Layout entities control how your app surfaces inside Twenty's UI — what lives in the sidebar, which saved views ship with the app, and how a record detail page is arranged.
|
||||
|
||||
## Layout concepts
|
||||
|
||||
| Concept | What it controls | Entita |
|
||||
| ------------------------ | --------------------------------------------------------------------------------- | -------------------------- |
|
||||
| **View** | A saved list configuration for an object — visible fields, order, filters, groups | `defineView` |
|
||||
| **Navigation Menu Item** | An entry in the left sidebar that links to a view or an external URL | `defineNavigationMenuItem` |
|
||||
| **Page Layout** | The tabs and widgets that make up a record's detail page | `definePageLayout` |
|
||||
|
||||
Views, navigation items, and page layouts reference each other by `universalIdentifier`:
|
||||
|
||||
* A **navigation menu item** of type `VIEW` points at a `defineView` identifier, so the sidebar link opens that saved view.
|
||||
* A **page layout** of type `RECORD_PAGE` targets an object and can embed [front components](/l/cs/developers/extend/apps/front-components) inside its tabs as widgets.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineView" description="Definujte uložená zobrazení pro objekty">
|
||||
|
||||
Zobrazení jsou uložené konfigurace toho, jak se zobrazují záznamy objektu — včetně toho, která pole jsou viditelná, jejich pořadí a jaké filtry či seskupení jsou použity. Pomocí `defineView()` můžete k aplikaci přidat předkonfigurovaná zobrazení:
|
||||
|
||||
```ts src/views/example-view.ts
|
||||
import { defineView, ViewKey } 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',
|
||||
key: ViewKey.INDEX,
|
||||
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.
|
||||
* `key` určuje typ zobrazení (např. `ViewKey.INDEX` pro hlavní seznam).
|
||||
* `fields` určuje, které sloupce se zobrazí a v jakém pořadí. Každé pole odkazuje na `fieldMetadataUniversalIdentifier`.
|
||||
* Pro pokročilejší konfigurace můžete definovat také `filters`, `filterGroups`, `groups` a `fieldGroups`.
|
||||
* `position` určuje pořadí, pokud pro stejný objekt existuje více zobrazení.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="defineNavigationMenuItem" description="Definujte odkazy postranní navigace">
|
||||
|
||||
Položky navigační nabídky přidávají vlastní položky do postranního panelu pracovního prostoru. Použijte `defineNavigationMenuItem()` k odkazování na zobrazení, externí URL nebo objekty:
|
||||
|
||||
```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 menu odkazuje: `NavigationMenuItemType.VIEW` pro uložené zobrazení nebo `NavigationMenuItemType.LINK` pro externí URL.
|
||||
* Pro odkazy na zobrazení nastavte `viewUniversalIdentifier`. Pro externí odkazy nastavte `link`.
|
||||
* `position` určuje pořadí v postranním panelu.
|
||||
* `icon` a `color` (volitelné) upravují vzhled.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePageLayout" description="Definujte vlastní rozvržení stránek pro zobrazení záznamů">
|
||||
|
||||
Rozvržení stránek vám umožní přizpůsobit vzhled stránky s detailem záznamu — které karty se zobrazí, jaké widgety jsou uvnitř každé karty a jak jsou uspořádány. Pomocí `definePageLayout()` můžete k aplikaci přidat vlastní rozvržení:
|
||||
|
||||
```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.CANVAS,
|
||||
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 obvykle `'RECORD_PAGE'` pro úpravu 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` (`CANVAS` pro volné rozvržení).
|
||||
* Každý `widget` uvnitř karty může vykreslit frontendovou komponentu, seznam relací nebo jiné vestavěné typy widgetů.
|
||||
* `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é.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
@@ -0,0 +1,560 @@
|
||||
---
|
||||
title: Logické funkce
|
||||
description: Define server-side TypeScript functions with HTTP, cron, and database event triggers.
|
||||
icon: bolt
|
||||
---
|
||||
|
||||
Logic functions are server-side TypeScript functions that run on the Twenty platform. They can be triggered by HTTP requests, cron schedules, or database events — and can also be exposed as tools for AI agents.
|
||||
|
||||
<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 { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk/define';
|
||||
import { CoreApiClient, type Person } from 'twenty-client-sdk/core';
|
||||
|
||||
const handler = async (params: RoutePayload) => {
|
||||
const client = new CoreApiClient();
|
||||
const name = 'name' in params.queryStringParameters
|
||||
? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world'
|
||||
: '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: 'GET',
|
||||
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ě **pod koncovým bodem `/s/`**:
|
||||
> např. `path: '/post-card/create'` je volatelné na `https://your-twenty-server.com/s/post-card/create`
|
||||
* **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.*`
|
||||
|
||||
<Note>
|
||||
Funkci můžete také spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -n create-new-post-card -p '{"key": "value"}'
|
||||
```
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf
|
||||
```
|
||||
|
||||
Logy můžete sledovat pomocí:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty 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`:
|
||||
|
||||
```ts
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
||||
|
||||
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 }` |
|
||||
| `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>
|
||||
|
||||
#### Zpřístupnění funkce jako nástroje
|
||||
|
||||
Logické funkce lze zpřístupnit jako **nástroje** pro agenty AI a pracovní postupy. Když je funkce označena jako nástroj, stane se dohledatelnou funkcemi AI produktu Twenty a lze ji použít v automatizacích pracovních postupů.
|
||||
|
||||
Chcete-li označit logickou funkci jako nástroj, nastavte `isTool: true`:
|
||||
|
||||
```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,
|
||||
isTool: true,
|
||||
});
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Můžete kombinovat `isTool` se spouštěči — funkce může být zároveň nástrojem (volatelným agenty AI) a současně se spouštět událostmi.
|
||||
* **`toolInputSchema`** (volitelné): Objekt JSON Schema, který popisuje parametry, jež vaše funkce přijímá. Schéma se určuje automaticky ze statické analýzy zdrojového kódu, ale můžete ho nastavit i explicitně:
|
||||
|
||||
```ts
|
||||
export default defineLogicFunction({
|
||||
...,
|
||||
toolInputSchema: {
|
||||
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'],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<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>
|
||||
<Accordion title="definePostInstallLogicFunction" description="Definujte postinstalační logickou funkci (jedna na aplikaci)">
|
||||
|
||||
Postinstalační funkce je logická funkce, která se spustí automaticky, jakmile je instalace vaší aplikace v pracovním prostoru dokončena. Server ji provede **poté**, co byla synchronizována metadata aplikace a vygenerován klient SDK, takže je pracovní prostor plně připraven k použití a nové schéma je zavedeno. Mezi typické případy použití patří naplnění výchozími daty, vytvoření počátečních záznamů, konfigurace nastavení pracovního prostoru nebo zřizování prostředků ve službách třetích stran.
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (payload: InstallPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: false,
|
||||
shouldRunSynchronously: false,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --postInstall
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Obslužná funkce obdrží `InstallPayload` s `{ previousVersion?: string; newVersion: string }` — `newVersion` je verze, která se instaluje, a `previousVersion` je verze, která byla nainstalována dříve (nebo `undefined` při čisté instalaci). Tyto hodnoty použijte k rozlišení čistých instalací od aktualizací a ke spuštění migrační logiky specifické pro verzi.
|
||||
* **Kdy se hook spouští**: ve výchozím nastavení pouze při čistých instalacích. Předejte `shouldRunOnVersionUpgrade: true`, pokud chcete, aby se spouštěl i při aktualizaci aplikace z předchozí verze. Pokud je vynechán, příznak má výchozí hodnotu `false` a při aktualizacích se hook přeskočí.
|
||||
* **Model provádění — ve výchozím nastavení asynchronní, synchronní volitelně**: příznak `shouldRunSynchronously` určuje *jak* se spouští post-install.
|
||||
* `shouldRunSynchronously: false` *(výchozí)* — hook je **zařazen do fronty zpráv** s `retryLimit: 3` a běží asynchronně ve workeru. Odezva instalace se vrátí hned po zařazení úlohy do fronty, takže pomalá nebo chybující obslužná funkce neblokuje volajícího. Worker se pokusí o opakování až třikrát. **Použijte pro dlouho běžící úlohy** — plnění velkých datových sad, volání pomalých externích API, zřizování externích prostředků, cokoli, co by mohlo přesáhnout rozumné časové okno HTTP odezvy.
|
||||
* `shouldRunSynchronously: true` — hook se provádí **inline během instalačního procesu** (stejný vykonavatel jako pre-install). Instalační požadavek blokuje, dokud obslužná funkce nedokončí, a pokud vyvolá výjimku, volající instalace obdrží `POST_INSTALL_ERROR`. Žádné automatické opakování. **Použijte pro rychlé úlohy, které se musí dokončit před odpovědí** — například vrácení validační chyby uživateli nebo rychlé nastavení, na kterém bude klient záviset ihned po návratu volání instalace. Mějte na paměti, že v době, kdy se spustí post-install, už byla migrace metadat aplikována, takže selhání v synchronním režimu změny schématu **ne**vrací zpět — pouze odhalí chybu.
|
||||
* Ujistěte se, že vaše obslužná funkce je idempotentní. V asynchronním režimu se může fronta pokusit až třikrát; v obou režimech se může hook znovu spustit při aktualizacích, pokud je `shouldRunOnVersionUpgrade: true`.
|
||||
* Proměnné prostředí `APPLICATION_ID`, `APP_ACCESS_TOKEN` a `API_URL` jsou dostupné uvnitř obslužné funkce (stejně jako u jakékoli jiné logické funkce), takže můžete volat Twenty API s aplikačním přístupovým tokenem omezeným na vaši aplikaci.
|
||||
* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna.
|
||||
* Atributy funkce `universalIdentifier`, `shouldRunOnVersionUpgrade` a `shouldRunSynchronously` jsou během buildu automaticky připojeny k manifestu aplikace do pole `postInstallLogicFunction` — není potřeba je uvádět v `defineApplication()`.
|
||||
* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty.
|
||||
* **Nespouští se v režimu dev**: když je aplikace registrována lokálně (pomocí `yarn twenty dev`), server zcela přeskočí instalační tok a synchronizuje soubory přímo prostřednictvím sledovače CLI — takže se post-install v režimu dev nikdy nespustí bez ohledu na `shouldRunSynchronously`. Použijte `yarn twenty exec --postInstall` k ručnímu spuštění nad běžícím pracovním prostorem.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="definePreInstallLogicFunction" description="Definujte předinstalační logickou funkci (jedna na aplikaci)">
|
||||
|
||||
Funkce pre-install je logická funkce, která se během instalace spouští automaticky, **před aplikováním migrace metadat pracovního prostoru**. Má stejný tvar payloadu jako post-install (`InstallPayload`), ale je zařazena dříve v instalačním toku, aby mohla připravit stav, na němž nadcházející migrace závisí — typické použití zahrnuje zálohování dat, ověření kompatibility s novým schématem nebo archivaci záznamů, které se chystají přeuspořádat nebo odstranit.
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
|
||||
const handler = async (payload: InstallPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: 'a1b2c3d4-5678-90ab-cdef-1234567890ab',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: true,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty exec --preInstall
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
* Funkce pre-install používají `definePreInstallLogicFunction()` — stejné specializované nastavení jako u post-install, pouze připojené k jiné fázi životního cyklu.
|
||||
* Obě obslužné funkce pre- i post-install přijímají stejný typ `InstallPayload`: `{ previousVersion?: string; newVersion: string }`. Importujte jej jednou a znovu použijte pro oba hooky.
|
||||
* **Kdy se hook spouští**: umístěn těsně před migrací metadat pracovního prostoru (`synchronizeFromManifest`). Před spuštěním server provede čistě aditivní "zjednodušenou synchronizaci", která v metadatech pracovního prostoru zaregistruje pre-install funkci **nové** verze — ničeho dalšího se nedotkne — a poté ji spustí. Protože tato synchronizace je pouze aditivní, objekty, pole a data předchozí verze zůstávají při spuštění vaší obslužné funkce zachována: můžete bezpečně číst a zálohovat stav před migrací.
|
||||
* **Model provádění**: pre-install se provádí **synchronně** a **blokuje instalaci**. Pokud obslužná funkce vyvolá výjimku, instalace se přeruší ještě před aplikováním jakýchkoli změn 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.
|
||||
* Stejně jako u post-install je na jednu aplikaci povolena pouze jedna funkce pre-install. Během buildu je automaticky připojena k manifestu aplikace pod `preInstallLogicFunction`.
|
||||
* **Nespouští se v režimu dev**: stejně jako u post-install — u lokálně registrovaných aplikací je instalační tok zcela přeskočen, takže se pre-install pod `yarn twenty dev` nikdy nespustí. Použijte `yarn twenty exec --preInstall` k ručnímu spuštění.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Pre-install vs post-install: kdy použít který" description="Výběr správného instalačního hooku">
|
||||
|
||||
Oba hooky jsou součástí téhož instalačního toku a přijímají stejný `InstallPayload`. Rozdíl je v tom, **kdy** se spouštějí vzhledem k migraci metadat pracovního prostoru, a to určuje, jakých dat se mohou bezpečně dotýkat.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ install flow │
|
||||
│ │
|
||||
│ upload package → [pre-install] → metadata migration → │
|
||||
│ generate SDK → [post-install] │
|
||||
│ │
|
||||
│ old schema visible new schema visible │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Pre-install je vždy **synchronní** (blokuje instalaci a může ji přerušit). Post-install je **ve výchozím nastavení asynchronní** — zařazen do workeru s automatickými pokusy o opakování — ale může přejít na synchronní provádění pomocí `shouldRunSynchronously: true`. Viz accordion `definePostInstallLogicFunction` výše, kdy použít jednotlivé režimy.
|
||||
|
||||
**Použijte `post-install` pro cokoli, co vyžaduje existenci nového schématu.** To je běžný případ:
|
||||
|
||||
* Plnění výchozími daty (vytváření počátečních záznamů, výchozích pohledů, demo obsahu) vůči nově přidaným objektům a polím.
|
||||
* Registrace webhooků u služeb třetích stran poté, co má aplikace své přihlašovací údaje.
|
||||
* Volání vlastního API k dokončení nastavení, které závisí na synchronizovaných metadatech.
|
||||
* Idempotentní logika "zajisti, že to existuje", která má při každé aktualizaci uvést stav do souladu — kombinujte s `shouldRunOnVersionUpgrade: true`.
|
||||
|
||||
Příklad — po instalaci naplňte výchozí záznam `PostCard`:
|
||||
|
||||
```ts src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
import { createClient } from './generated/client';
|
||||
|
||||
const handler = async ({ previousVersion }: InstallPayload): Promise<void> => {
|
||||
if (previousVersion) return; // fresh installs only
|
||||
|
||||
const client = createClient();
|
||||
await client.postCard.create({
|
||||
data: { title: 'Welcome to Postcard', content: 'Your first card!' },
|
||||
});
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: 'f7a2b9c1-3d4e-5678-abcd-ef9876543210',
|
||||
name: 'post-install',
|
||||
description: 'Seeds a welcome post card after install.',
|
||||
timeoutSeconds: 300,
|
||||
shouldRunOnVersionUpgrade: false,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
**Použijte `pre-install`, pokud by migrace jinak zničila nebo poškodila existující data.** Protože pre-install běží proti *předchozímu* schématu a jeho selhání vrací aktualizaci zpět, je to správné místo pro cokoli rizikového:
|
||||
|
||||
* **Zálohování dat, která se chystají odstranit nebo přeuspořádat** — např. odstraňujete pole ve verzi v2 a potřebujete jeho hodnoty zkopírovat do jiného pole nebo je před spuštěním migrace exportovat do úložiště.
|
||||
* **Archivace záznamů, které by nové omezení zneplatnilo** — např. pole se stává `NOT NULL` a je třeba nejprve smazat nebo opravit řádky s hodnotami null.
|
||||
* **Ověření kompatibility a odmítnutí aktualizace, pokud nelze aktuální data čistě migrovat** — vyhoďte výjimku z obslužné funkce a instalace se ukončí bez provedených změn. Je to bezpečnější, než zjistit nekompatibilitu uprostřed migrace.
|
||||
* **Přejmenování nebo změna klíčů dat** před změnou schématu, která by ztratila vazby.
|
||||
|
||||
Příklad — archivujte záznamy před destruktivní migrací:
|
||||
|
||||
```ts src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallPayload } from 'twenty-sdk/define';
|
||||
import { createClient } from './generated/client';
|
||||
|
||||
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 = createClient();
|
||||
const legacyRecords = await client.postCard.findMany({
|
||||
where: { notes: { isNotNull: true } },
|
||||
});
|
||||
|
||||
if (legacyRecords.length === 0) return;
|
||||
|
||||
// Copy legacy `notes` into the new `description` field before the migration
|
||||
// drops the `notes` column. If this fails, the upgrade is aborted and the
|
||||
// workspace stays on v1 with all data intact.
|
||||
await Promise.all(
|
||||
legacyRecords.map((record) =>
|
||||
client.postCard.update({
|
||||
where: { id: record.id },
|
||||
data: { description: record.notes },
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
```
|
||||
|
||||
**Zlaté pravidlo:**
|
||||
|
||||
| You want to... | Použít |
|
||||
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Naplňte výchozí data, nakonfigurujte pracovní prostor, zaregistrujte externí prostředky | `post-install` |
|
||||
| Spusťte dlouho běžící plnění nebo volání třetích stran, která by neměla blokovat odezvu instalace | `post-install` (výchozí — `shouldRunSynchronously: false`, s opakovanými pokusy workeru) |
|
||||
| Spusťte rychlé nastavení, na které bude volající spoléhat ihned po návratu volání 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) |
|
||||
| Spouštějte srovnání stavu při každé aktualizaci | `post-install` s `shouldRunOnVersionUpgrade: true` |
|
||||
| Proveďte jednorázové nastavení pouze při první instalaci | `post-install` s `shouldRunOnVersionUpgrade: false` (výchozí) |
|
||||
|
||||
<Note>
|
||||
Pokud si nejste jisti, 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í.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## 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 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 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ů.
|
||||
|
||||
```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íč s rozsahem omezeným na výchozí roli funkce vaší aplikace
|
||||
|
||||
Není nutné je předávat klientům — čtou je automaticky z `process.env`. Oprávnění API klíče jsou určena rolí uvedenou v `defaultRoleUniversalIdentifier` ve vašem `application-config.ts`.
|
||||
</Note>
|
||||
@@ -1,12 +1,9 @@
|
||||
---
|
||||
title: Publikování
|
||||
icon: nahrát
|
||||
description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte interně.
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
## Přehled
|
||||
|
||||
Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat:
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Dovednosti a agenti
|
||||
description: Define AI skills and agents for your app.
|
||||
icon: robot
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Skills and agents are currently in alpha. Funkce funguje, ale stále se vyvíjí.
|
||||
</Warning>
|
||||
|
||||
Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="defineSkill" description="Definujte dovednosti agenta 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.
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
title: OAuth
|
||||
icon: klíč
|
||||
description: Authorization code flow with PKCE and client credentials for server-to-server access.
|
||||
---
|
||||
|
||||
Twenty implements OAuth 2.0 with authorization code + PKCE for user-facing apps and client credentials for server-to-server access. Clients are registered dynamically via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) — no manual setup in a dashboard.
|
||||
|
||||
## When to Use OAuth
|
||||
|
||||
| Scénář | Auth Method |
|
||||
| --------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| Internal scripts, automation | [API Key](/l/cs/developers/extend/api#authentication) |
|
||||
| External app acting on behalf of a user | **OAuth — Authorization Code** |
|
||||
| Server-to-server, no user context | **OAuth — Client Credentials** |
|
||||
| Twenty App with UI extensions | [Apps](/l/cs/developers/extend/apps/getting-started) (OAuth is handled automatically) |
|
||||
|
||||
## Register a Client
|
||||
|
||||
Twenty supports **dynamic client registration** per [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). No manual setup needed — register programmatically:
|
||||
|
||||
```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"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": "abc123",
|
||||
"client_secret": "secret456",
|
||||
"client_name": "My Integration",
|
||||
"redirect_uris": ["https://myapp.com/callback"]
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Store the `client_secret` securely — it cannot be retrieved later.
|
||||
</Warning>
|
||||
|
||||
## Oprávnění
|
||||
|
||||
| Scope | Přístup |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| `api` | Full read/write access to the Core and Metadata APIs |
|
||||
| `profil` | Read the authenticated user's profile information |
|
||||
|
||||
Request scopes as a space-separated string: `scope=api profile`
|
||||
|
||||
## Authorization Code Flow
|
||||
|
||||
Use this flow when your app acts on behalf of a Twenty user.
|
||||
|
||||
### 1. Redirect the user to authorize
|
||||
|
||||
```
|
||||
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 | Your registered client ID |
|
||||
| `response_type` | Ano | Must be `code` |
|
||||
| `redirect_uri` | Ano | Must match a registered redirect URI |
|
||||
| `scope` | Ne | Space-separated scopes (defaults to `api`) |
|
||||
| `stav` | Doporučeno | Random string to prevent CSRF attacks |
|
||||
| `code_challenge` | Doporučeno | PKCE challenge (SHA-256 hash of verifier, base64url-encoded) |
|
||||
| `code_challenge_method` | Doporučeno | Must be `S256` when using PKCE |
|
||||
|
||||
The user sees a consent screen and approves or denies access.
|
||||
|
||||
### 2. Handle the callback
|
||||
|
||||
After authorization, Twenty redirects back to your `redirect_uri`:
|
||||
|
||||
```
|
||||
https://myapp.com/callback?code=AUTH_CODE&state=random_state_value
|
||||
```
|
||||
|
||||
Verify that `state` matches what you sent.
|
||||
|
||||
### 3. Exchange the code for tokens
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbG...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "dGhpcyBpcyBh..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Use the access token
|
||||
|
||||
```bash
|
||||
GET /rest/companies
|
||||
Authorization: Bearer ACCESS_TOKEN
|
||||
```
|
||||
|
||||
### 5. Refresh when expired
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Client Credentials Flow
|
||||
|
||||
For server-to-server integrations with no user interaction:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
The returned token has workspace-level access, not tied to any specific user.
|
||||
|
||||
## Server Discovery
|
||||
|
||||
Twenty publishes its OAuth configuration at a standard discovery endpoint:
|
||||
|
||||
```
|
||||
GET /.well-known/oauth-authorization-server
|
||||
```
|
||||
|
||||
This returns all endpoints, supported grant types, scopes, and capabilities — useful for building generic OAuth clients.
|
||||
|
||||
## API Endpoints Summary
|
||||
|
||||
| Koncový bod | Účel |
|
||||
| ----------------------------------------- | --------------------------- |
|
||||
| `/.well-known/oauth-authorization-server` | Server metadata discovery |
|
||||
| `/oauth/register` | Dynamic client registration |
|
||||
| `/oauth/authorize` | User authorization |
|
||||
| `/oauth/token` | Token exchange and refresh |
|
||||
|
||||
| Prostředí | Základní URL |
|
||||
| ------------------- | ------------------------ |
|
||||
| **Cloud** | `https://api.twenty.com` |
|
||||
| **Vlastní hosting** | `https://{your-domain}` |
|
||||
|
||||
## OAuth vs API Keys
|
||||
|
||||
| | API Klíče | OAuth |
|
||||
| ------------------ | ----------------------- | -------------------------------------- |
|
||||
| **Nastavení** | Generate in Settings | Register a client, implement flow |
|
||||
| **User context** | None (workspace-level) | Specific user's permissions |
|
||||
| **Vhodné pro** | Scripts, internal tools | External apps, multi-user integrations |
|
||||
| **Token rotation** | Ruční | Automatic via refresh tokens |
|
||||
| **Scoped access** | Full API access | Granular via scopes |
|
||||
@@ -1,11 +1,12 @@
|
||||
---
|
||||
title: Webhooky
|
||||
description: Dostávejte oznámení v reálném čase, když ve vašem CRM dojde k událostem.
|
||||
icon: satellite-dish
|
||||
description: Get notified when records change — HTTP POST to your endpoint on every create, update, or delete.
|
||||
---
|
||||
|
||||
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í.
|
||||
Twenty sends an HTTP POST to your URL whenever a record is created, updated, or deleted. All object types are covered, including custom objects.
|
||||
|
||||
## Vytvořit Webhook
|
||||
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
---
|
||||
title: Začínáme
|
||||
description: Vítejte v dokumentaci pro vývojáře Twenty, která je vaším zdrojem informací pro rozšiřování, vlastní hostování a přispívání do Twenty.
|
||||
title: Vývojáři
|
||||
description: Build apps, use the API, self-host, or contribute to the codebase.
|
||||
---
|
||||
|
||||
import { CardTitle } from "/snippets/card-title.mdx"
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card href="/l/cs/developers/extend/extend" img="/images/user-guide/integrations/plug.png">
|
||||
<CardTitle>Rozšiřte</CardTitle>
|
||||
Vytvářejte integrace pomocí API, webhooků a vlastních aplikací.
|
||||
<Card href="/l/cs/developers/extend/apps/getting-started" img="/images/user-guide/halftone/dev-apps.png">
|
||||
<CardTitle>Apps</CardTitle>
|
||||
Extend Twenty with custom objects, server-side logic, UI components, and AI agents — all as TypeScript packages.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/cs/developers/self-host/self-host" img="/images/user-guide/what-is-twenty/20.png">
|
||||
<CardTitle>Hostujte sami</CardTitle>
|
||||
Nasaďte a spravujte Twenty na vlastní infrastruktuře.
|
||||
<Card href="/l/cs/developers/extend/api" img="/images/user-guide/halftone/dev-api.png">
|
||||
<CardTitle>API</CardTitle>
|
||||
REST and GraphQL APIs, webhooks, and OAuth.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/cs/developers/contribute/contribute" img="/images/user-guide/github/github-header.png">
|
||||
<CardTitle>Přispějte</CardTitle>
|
||||
Připojte se k naší open-source komunitě a přispívejte do Twenty.
|
||||
<Card href="/l/cs/developers/self-host/capabilities/docker-compose" img="/images/user-guide/halftone/dev-self-host.png">
|
||||
<CardTitle>Self-Host</CardTitle>
|
||||
Run Twenty on your own infrastructure.
|
||||
</Card>
|
||||
|
||||
<Card href="/l/cs/developers/contribute/capabilities/local-setup" img="/images/user-guide/halftone/dev-contribute.png">
|
||||
<CardTitle>Contribute</CardTitle>
|
||||
Set up the monorepo locally and submit PRs.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Další metody
|
||||
icon: cloud
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Docker Compose jedním kliknutím
|
||||
title: Docker Compose
|
||||
icon: docker
|
||||
---
|
||||
|
||||
<Warning>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Nastavení
|
||||
icon: gear
|
||||
---
|
||||
|
||||
# Správa konfigurace
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Řešení potíží
|
||||
icon: wrench
|
||||
---
|
||||
|
||||
## Řešení potíží
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Průvodce upgradem
|
||||
icon: arrow-up-right-dots
|
||||
---
|
||||
|
||||
## Obecné pokyny
|
||||
@@ -16,366 +17,14 @@ Pokud jste použili Docker Compose, postupujte takto:
|
||||
|
||||
3. Opětovně zapněte Twenty pomocí `docker compose up -d`
|
||||
|
||||
Chcete-li upgradovat svou instanci o několik verzí, například z v0.33.0 na v0.35.0, musíte svou instanci postupně upgradovat, v tomto příkladu z v0.33.0 na v0.34.0 a poté z v0.34.0 na v0.35.0.
|
||||
|
||||
**Ujistěte se, že máte po každém upgradu nepoškozenou zálohu.**
|
||||
|
||||
## Kroky upgradu specifické pro danou verzi
|
||||
|
||||
## v1.0
|
||||
## After v1.21
|
||||
|
||||
Ahoj Twenty v1.0! 🎉
|
||||
We know support sequential upgrades. You don't need to go through each version one by one.
|
||||
|
||||
## v0.60
|
||||
## Before v1.21
|
||||
|
||||
### Vylepšení výkonu
|
||||
|
||||
Všechny interakce s metadatovým API byly optimalizovány pro lepší výkon, zejména pro manipulaci s metadaty objektů a operace vytváření pracovních prostorů.
|
||||
|
||||
Refaktorovali jsme naši strategii mezipaměti tak, aby upřednostňovala, pokud je to možné, zásahy do mezipaměti před databázovými dotazy, což výrazně zlepšilo výkon operací rozhraní API metadat.
|
||||
|
||||
Pokud po upgradu narazíte na problémy s výkonem, může být nutné vyprázdnit cache, aby bylo zajištěno její sladění s nejnovějšími změnami. Spusťte tento příkaz v kontejneru twenty-server:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
### v0.55
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.55 image
|
||||
|
||||
Už nemusíte spouštět žádný příkaz, nový obraz se automaticky postará o spuštění všech požadovaných migrací.
|
||||
|
||||
### Chyba `Uživatel nemá oprávnění`
|
||||
|
||||
Pokud po upgradu narazíte na chyby autorizace na většině požadavků, může být nutné vyprázdnit cache, abyste znovu provedli vyhodnocení nejnovějších oprávnění.
|
||||
|
||||
Ve svém kontejneru `twenty-server` spusťte:
|
||||
|
||||
```bash
|
||||
yarn command:prod cache:flush
|
||||
```
|
||||
|
||||
Tento problém je specifický pro tuto verzi Twenty a neměl by být vyžadován pro budoucí upgrady.
|
||||
|
||||
### v0.54
|
||||
|
||||
Od verze `0.53`, nejsou nutné žádné manuální akce.
|
||||
|
||||
#### Omezení metadatového schématu
|
||||
|
||||
Sloučili jsme schéma `metadata` do `core`, abychom zjednodušili načítání dat z `TypeORM`.
|
||||
Sloučili jsme krok příkazu `migrate` do příkazu `upgrade`. Nedoporučujeme ručně spouštět `migrate` v žádném z vašich kontejnerů server/worker.
|
||||
|
||||
### Od v0.53
|
||||
|
||||
Od `0.53` je upgrade programově upraven v rámci `DockerFile`, což znamená, že od této chvíle již nemusíte ručně spouštět žádný příkaz.
|
||||
|
||||
Ujistěte se, že upgradujete svou instanci postupně, aniž byste přeskočili hlavní verzi (např. `0.43.3` na `0.44.0` je povoleno, ale `0.43.1` na `0.45.0` nikoli), aby se předešlo asynchronizaci verzí pracovního prostoru, což by mohlo mít za následek chybu při běhu a chybějící funkčnost.
|
||||
|
||||
Chcete-li zkontrolovat, zda byl pracovní prostor správně migrován, můžete zkontrolovat jeho verzi v databázi v tabulce `core.workspace`.
|
||||
|
||||
Měla by se vždy nacházet v rozmezí vaší aktuální instance Twenty `major.minor` verze. Můžete zkontrolovat verzi instance na ovládacím panelu administrátora (na `/settings/admin-panel`, přístupné, pokud máte v databázi nastaveno uživatelské vlastnosti `canAccessFullAdminPanel` na true) nebo spuštěním `echo $APP_VERSION` ve vašem kontejneru `twenty-server`.
|
||||
|
||||
Chcete-li opravit asynchronizaci verzí pracovního prostoru, budete muset upgradovat z odpovídající verze twenty podle souvisejícího průvodce upgradem po jednotlivých krocích, až do dosažení požadované verze.
|
||||
|
||||
#### Odstranění `auditLog`
|
||||
|
||||
Odstranili jsme standardní objekt auditLog, což znamená, že váš záložní soubor může být po této migraci výrazně zmenšen.
|
||||
|
||||
### v0.51 až v0.52
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.52 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### Mám pracovní prostor zablokovaný ve verzi mezi `0.52.0` a `0.52.6`
|
||||
|
||||
Bohužel `0.52.0` a `0.52.6` byly zcela odstraněny z dockerHub.
|
||||
Budete muset ručně změnit verzi pracovního prostoru na `0.51.0` v databázi a upgradovat pomocí twenty verze `0.52.11` podle jejího výše uvedeného průvodce upgradem.
|
||||
|
||||
### v0.50 až v0.51
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.51 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### v0.44.0 až v0.50.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.50.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
#### Mutace Docker-compose.yml
|
||||
|
||||
Tato verze obsahuje mutaci `docker-compose.yml`, která zajišťuje, že služba `worker` má přístup k objemu `server-local-data`.
|
||||
Aktualizujte svůj místní `docker-compose.yml` pomocí [docker-compose.yml v0.50.0](https://github.com/twentyhq/twenty/blob/v0.50.0/packages/twenty-docker/docker-compose.yml)
|
||||
|
||||
### v0.43.0 až v0.44.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.44.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
### v0.42.0 až v0.43.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.43.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade
|
||||
```
|
||||
|
||||
V této verzi jsme také přešli na obraz postgres:16 v docker-compose.yml.
|
||||
|
||||
#### (Možnost 1) Migrace databáze
|
||||
|
||||
Zachování existujícího obrazu postgres-spilo je v pořádku, ale budete muset zmrazit verzi v `docker-compose.yml` na 0.43.0.
|
||||
|
||||
#### (Možnost 2) Migrace databáze
|
||||
|
||||
Pokud chcete migrovat svou databázi na nový obraz postgres:16, postupujte podle těchto kroků:
|
||||
|
||||
1. Zálohujte svou databázi z kontejneru postgres-spilo
|
||||
|
||||
```
|
||||
docker exec -it twenty-db-1 sh
|
||||
pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql
|
||||
exit
|
||||
docker cp twenty-db-1:/home/postgres/databases_backup.sql .
|
||||
```
|
||||
|
||||
Ujistěte se, že váš záložní soubor není prázdný.
|
||||
|
||||
2. Upgradujte svůj `docker-compose.yml` na použití obrazu postgres:16 podle [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) soubor.
|
||||
|
||||
3. Obnovte databázi do nového kontejneru postgres:16
|
||||
|
||||
```
|
||||
docker cp databases_backup.sql twenty-db-1:/databases_backup.sql
|
||||
docker exec -it twenty-db-1 sh
|
||||
psql -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} -f databases_backup.sql
|
||||
exit
|
||||
```
|
||||
|
||||
### v0.41.0 až v0.42.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.42.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.42
|
||||
```
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Odstraněno: `FRONT_PORT`, `FRONT_PROTOCOL`, `FRONT_DOMAIN`, `PORT`
|
||||
* Přidáno: `FRONTEND_URL`, `NODE_PORT`, `MAX_NUMBER_OF_WORKSPACES_DELETED_PER_EXECUTION`, `MESSAGING_PROVIDER_MICROSOFT_ENABLED`, `CALENDAR_PROVIDER_MICROSOFT_ENABLED`, `IS_MICROSOFT_SYNC_ENABLED`
|
||||
|
||||
### v0.40.0 až v0.41.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.41.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.41
|
||||
```
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Odstraněno: `AUTH_MICROSOFT_TENANT_ID`
|
||||
|
||||
### v0.35.0 až v0.40.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.40.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.40
|
||||
```
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Přidáno: `IS_EMAIL_VERIFICATION_REQUIRED`, `EMAIL_VERIFICATION_TOKEN_EXPIRES_IN`, `WORKFLOW_EXEC_THROTTLE_LIMIT`, `WORKFLOW_EXEC_THROTTLE_TTL`
|
||||
|
||||
### v0.34.0 až v0.35.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.35.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.35
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.35` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Nahradili jsme `ENABLE_DB_MIGRATIONS` s `DISABLE_DB_MIGRATIONS` (výchozí hodnota je nyní `false`, pravděpodobně nemusíte nastavovat nic)
|
||||
|
||||
### v0.33.0 až v0.34.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.34.0 image
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.34
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.34` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
* Odstraněno: `FRONT_BASE_URL`
|
||||
* Přidáno: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
|
||||
|
||||
Aktualizovali jsme způsob, jakým zpracováváme frontend URL.
|
||||
Nyní můžete nastavit frontend URL pomocí proměnných `FRONT_DOMAIN`, `FRONT_PROTOCOL` a `FRONT_PORT`.
|
||||
Pokud není FRONT_DOMAIN nastavena, frontend URL se vrátí na `SERVER_URL`.
|
||||
|
||||
### v0.32.0 až v0.33.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.33.0 image
|
||||
|
||||
```
|
||||
yarn command:prod cache:flush
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.33
|
||||
```
|
||||
|
||||
Příkaz `yarn command:prod cache:flush` vyprázdní cache Redis.
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.33` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
Od této verze se obraz twenty-postgres pro DB stal zastaralým a místo něj se používá twenty-postgres-spilo.
|
||||
Pokud chcete pokračovat v používání obrazu twenty-postgres, jednoduše nahraďte `twentycrm/twenty-postgres:${TAG}` za `twentycrm/twenty-postgres` v docker-compose.yml.
|
||||
|
||||
### v0.31.0 až v0.32.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.32.0 image
|
||||
|
||||
**Migrace schématu a dat**
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.32
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.32` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
**Proměnné prostředí**
|
||||
|
||||
Aktualizovali jsme způsob, jakým zpracováváme připojení k Redis.
|
||||
|
||||
* Odstraněno: `REDIS_HOST`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`
|
||||
* Přidáno: `REDIS_URL`
|
||||
|
||||
Aktualizujte svůj `.env` soubor tak, aby používal novou proměnnou `REDIS_URL` místo jednotlivých parametrů připojení k Redis.
|
||||
|
||||
Také jsme zjednodušili způsob, jakým zpracováváme tokeny JWT.
|
||||
|
||||
* Odstraněno: `ACCESS_TOKEN_SECRET`, `LOGIN_TOKEN_SECRET`, `REFRESH_TOKEN_SECRET`, `FILE_TOKEN_SECRET`
|
||||
* Přidáno: `APP_SECRET`
|
||||
|
||||
Aktualizujte svůj `.env` soubor tak, aby používal novou proměnnou `APP_SECRET` místo jednotlivých tokenů (můžete použít stejný tajný řetězec jako dříve nebo vygenerovat nový náhodný řetězec)
|
||||
|
||||
**Propojený účet**
|
||||
|
||||
Pokud používáte propojený účet k synchronizaci vašich emailů a kalendářů Google, budete muset aktivovat [People API](https://developers.google.com/people) na konzoli Google Admin.
|
||||
|
||||
### v0.30.0 až v0.31.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.31.0 image
|
||||
|
||||
**Migrace schématu a dat**:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.31
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.31` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
### v0.24.0 až v0.30.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.30.0 image
|
||||
|
||||
**Změna, která způsobí nekompatibilitu**:
|
||||
Pro zvýšení výkonu nyní vyžaduje Twenty konfiguraci cache Redis. Aktualizovali jsme náš [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml), aby to odrážel.
|
||||
Ujistěte se, že jste aktualizovali svou konfiguraci a své proměnné prostředí odpovídajícím způsobem:
|
||||
|
||||
```
|
||||
REDIS_HOST={váš-redis-host}
|
||||
REDIS_PORT={váš-redis-port}
|
||||
CACHE_STORAGE_TYPE=redis
|
||||
```
|
||||
|
||||
**Migrace schématu a dat**:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.30
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.30` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
### v0.23.0 až v0.24.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.24.0 image
|
||||
|
||||
Spusťte následující příkazy:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.24
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny struktury databáze (core a metadata schémata)
|
||||
Příkaz `yarn command:prod upgrade-0.24` se postará o datovou migraci všech pracovních míst.
|
||||
|
||||
### v0.22.0 až v0.23.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.23.0 image
|
||||
|
||||
Spusťte následující příkazy:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod upgrade-0.23
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny na databázi.
|
||||
Příkaz `yarn command:prod upgrade-0.23` se postará o datovou migraci, včetně přesunu aktivit na úkoly/poznámky.
|
||||
|
||||
### v0.21.0 až v0.22.0
|
||||
|
||||
Upgradujte svou instanci Twenty pro použití v0.22.0 image
|
||||
|
||||
Spusťte následující příkazy:
|
||||
|
||||
```
|
||||
yarn database:migrate:prod
|
||||
yarn command:prod workspace:sync-metadata -f
|
||||
yarn command:prod upgrade-0.22
|
||||
```
|
||||
|
||||
Příkaz `yarn database:migrate:prod` aplikuje změny na databázi.
|
||||
Příkaz `yarn command:prod workspace:sync-metadata -f` synchronizuje definici standardních objektů s tabulkami metadata a aplikuje nezbytné migrace na existující pracovní prostory.
|
||||
Příkaz `yarn command:prod upgrade-0.22` aplikuje specifické datové transformace pro adaptaci na nové objektové defaultRequestInstrumentationOptions.
|
||||
Make sure to go through every major tagged version when upgrading (upgrade v1.6.x to v.7.y, then v.7.y to v.8.z, etc.).
|
||||
|
||||
Reference in New Issue
Block a user