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