a3a6a55051
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com>
189 lines
13 KiB
Plaintext
189 lines
13 KiB
Plaintext
---
|
|
title: Hook di installazione
|
|
description: Esegui la logica durante il ciclo di vita di installazione, aggiornamento o disinstallazione — inserisci dati iniziali, esegui il backup dei record, valida l'aggiornamento, pulisci le risorse esterne.
|
|
icon: wrench
|
|
---
|
|
|
|
Gli hook di installazione sono funzioni logiche speciali che vengono eseguite durante il ciclo di vita di installazione, aggiornamento o disinstallazione. Condividono lo stesso runtime del gestore delle [logic functions](/l/it/developers/extend/apps/logic/logic-functions) normali, ma sono dichiarati con le proprie funzioni di definizione e vivono al di fuori del normale modello di trigger (HTTP, cron, eventi del database). Gli hook di installazione ricevono un `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` è `undefined` in caso di nuova installazione); l'hook di disinstallazione riceve un `UninstallPayload` (`{ version?: string }` — la versione che viene rimossa).
|
|
|
|
Ogni app può definire **al massimo uno** per ciascun hook (pre-install, post-install, uninstall). La build del manifesto genera un errore se viene rilevato più di un hook per qualsiasi tipo.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ install flow │
|
|
│ │
|
|
│ upload package → [pre-install] → metadata migration → │
|
|
│ generate SDK → [post-install] │
|
|
│ │
|
|
│ old schema visible new schema visible │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## A colpo d'occhio
|
|
|
|
| | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` |
|
|
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| Quando viene eseguito | Prima della migrazione dei metadati — lo schema e i dati **precedenti** sono ancora intatti | Dopo la migrazione e la generazione dell'SDK — il **nuovo** schema è in vigore |
|
|
| Esecuzione | Sempre sincrona; blocca l'installazione | Async per impostazione predefinita (in coda, 3 tentativi); modalità sync tramite opt-in con `shouldRunSynchronously: true` |
|
|
| In caso di errore | L'installazione viene **annullata** prima di qualsiasi modifica allo schema | Async: ritentato fino a 3 volte. Sync: il chiamante riceve `POST_INSTALL_ERROR` (le modifiche allo schema **non** vengono annullate) |
|
|
| Uso tipico | Eseguire il backup o correggere dati che una migrazione perderebbe; rifiutare un aggiornamento rischioso lanciando un'eccezione | Popolare dati predefiniti, configurare il workspace, registrare risorse esterne |
|
|
|
|
**Regola generale:** usa post-install come impostazione predefinita. Ricorri al pre-install solo quando la migrazione stessa è distruttiva e devi intercettare lo stato precedente prima che vada perso.
|
|
|
|
| Vuoi... | Usa |
|
|
| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
|
|
| Popolare i dati, configurare il workspace, registrare risorse esterne | `post-install` |
|
|
| Lavoro di lunga durata che non dovrebbe bloccare la risposta dell'installazione | `post-install` (modalità async predefinita, con retry del worker) |
|
|
| Eseguire un setup rapido da cui il chiamante dipende immediatamente dopo il completamento dell'installazione | `post-install` con `shouldRunSynchronously: true` |
|
|
| Leggere o eseguire il backup dei dati che la prossima migrazione perderebbe | `pre-install` |
|
|
| Rifiutare un aggiornamento che corromperebbe i dati esistenti | `pre-install` (genera un'eccezione dall'handler) |
|
|
| Riconciliazione a ogni aggiornamento | Uno qualsiasi dei due hook con `shouldRunOnVersionUpgrade: true` |
|
|
|
|
## Comportamento condiviso da entrambi gli hook
|
|
|
|
* La config è una config di `defineLogicFunction` meno le impostazioni di trigger, più `shouldRunOnVersionUpgrade`.
|
|
* **Quando viene eseguito**: solo sulle nuove installazioni, per impostazione predefinita. Imposta `shouldRunOnVersionUpgrade: true` per eseguirlo anche sugli upgrade. Usa `previousVersion` / `newVersion` per ramificare in base al percorso di upgrade.
|
|
* **L'idempotenza è importante**: il post-install async può essere ritentato e qualsiasi hook viene rieseguito sugli upgrade quando `shouldRunOnVersionUpgrade` è attivo.
|
|
* Il consueto ambiente delle logic-function (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) viene iniettato, così puoi chiamare le API di Twenty con il token della tua app.
|
|
* L'hook viene collegato automaticamente al manifesto dell'applicazione in fase di build (`preInstallLogicFunction` / `postInstallLogicFunction`) — non c'è nulla da referenziare in [`defineApplication()`](/l/it/developers/extend/apps/config/application).
|
|
* Il `timeoutSeconds` predefinito è 300 per consentire attività di setup più lunghe, come il seeding dei dati.
|
|
* **Non eseguito in modalità dev**: `yarn twenty dev` salta il flusso di installazione e sincronizza direttamente i file, quindi gli hook non vengono mai eseguiti in quell'ambiente. Attivali invece manualmente:
|
|
|
|
```bash filename="Terminal"
|
|
yarn twenty dev:function:exec --postInstall
|
|
yarn twenty dev:function:exec --preInstall
|
|
```
|
|
|
|
<AccordionGroup>
|
|
<Accordion title="definePostInstallLogicFunction" description="Viene eseguita dopo che la migrazione dei metadati dello spazio di lavoro è stata applicata">
|
|
|
|
Viene eseguito una volta che l'installazione della tua app è terminata: metadati sincronizzati, client SDK generato, nuovo schema interrogabile. Esempio — eseguire il seeding di un record predefinito nelle nuove installazioni:
|
|
|
|
```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,
|
|
});
|
|
```
|
|
|
|
Il flag `shouldRunSynchronously` controlla il modello di esecuzione:
|
|
|
|
* `false` *(predefinito)* — messo in coda nella message queue (`retryLimit: 3`) ed eseguito da un worker. La risposta dell'installazione ritorna non appena il job viene messo in coda. **Da usare per lavoro di lunga durata** — seeding di grandi dataset, API di terze parti lente.
|
|
* `true` — eseguito inline durante il flusso di installazione. La richiesta di installazione rimane bloccata finché l'handler non termina; un errore lanciato viene esposto al chiamante come `POST_INSTALL_ERROR` (nessun retry). **Da usare per lavoro rapido che deve completarsi prima della risposta.** La migrazione è già stata applicata a questo punto, quindi un errore non annulla le modifiche allo schema — si limita a esporre l'errore.
|
|
|
|
</Accordion>
|
|
<Accordion title="definePreInstallLogicFunction" description="Viene eseguita prima che la migrazione dei metadati dello spazio di lavoro sia applicata">
|
|
|
|
Viene eseguito prima della migrazione dei metadati, contro lo schema **precedente** — il posto giusto per eseguire il backup di dati che una migrazione perderebbe o per rifiutare un upgrade rischioso. Prima dell'esecuzione, il server esegue una "sincronizzazione ridotta" puramente additiva che registra solo la funzione di pre-install della versione nuova; tutto il resto — oggetti, campi e dati della versione precedente — rimane intatto quando il tuo handler viene eseguito.
|
|
|
|
Il pre-install è sempre **sincrono** e blocca l'installazione. Se l'handler genera un'eccezione, l'installazione viene interrotta prima che venga applicata qualsiasi modifica allo schema — il workspace rimane sulla versione precedente in uno stato coerente. Questo è intenzionale: il pre-install è la tua ultima possibilità per rifiutare un aggiornamento rischioso.
|
|
|
|
Esempio — copiare i valori di un campo legacy prima che la migrazione lo elimini:
|
|
|
|
```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>
|
|
|
|
## Hook di disinstallazione
|
|
|
|
`defineUninstallLogicFunction` dichiara un hook che viene eseguito quando un utente disinstalla la tua app. Viene eseguito **prima** che i metadati, i dati e il codice dell'app vengano rimossi — una volta eseguita la migrazione di eliminazione non rimane più nulla da eseguire — quindi il tuo gestore può ancora interrogare gli oggetti e i record dell'app. Usalo per la pulizia delle risorse esterne: deprovisioning delle risorse API, eliminazione dei bot rimanenti, revoca dei webhook.
|
|
|
|
Note:
|
|
|
|
* L'hook è "best-effort": viene eseguito in modo sincrono, ma un errore viene registrato e **non blocca mai la disinstallazione** — la pulizia non deve rendere impossibile rimuovere un'app.
|
|
* Riceve `UninstallPayload` (`{ version?: string }` — la versione che viene rimossa).
|
|
* Non viene eseguito quando un tentativo di nuova installazione non riuscito viene annullato — l'app non ha mai terminato l'installazione.
|
|
* L'hook non può essere eseguito dopo che l'app è stata rimossa, quindi la pulizia esterna che dipende dai dati dell'app (ad es. ID dei bot memorizzati nei record) deve essere eseguita qui, non in un job pianificato esterno.
|
|
* Come gli hook di installazione, **non viene eseguito in modalità dev** — attivalo manualmente invece:
|
|
|
|
```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,
|
|
});
|
|
```
|