--- title: Установочные хуки description: Выполняйте логику во время жизненного цикла установки, обновления или удаления — выполняйте начальное заполнение данными, создавайте резервные копии записей, проверяйте корректность обновления, очищайте внешние ресурсы. icon: wrench --- Установочные хуки — это специальные логические функции, которые выполняются во время жизненного цикла установки, обновления или удаления. Они используют то же окружение выполнения обработчика, что и обычные [logic functions](/l/ru/developers/extend/apps/logic/logic-functions), но объявляются с помощью собственных функций определения и существуют вне обычной модели триггеров (HTTP, cron, события базы данных). Установочные хуки получают `InstallPayload` (`{ previousVersion?: string; newVersion: string }` — `previousVersion` имеет значение `undefined` при новой установке); хук удаления получает `UninstallPayload` (`{ version?: string }` — версия, которая удаляется). Каждое приложение может определить **не более одного** хука каждого типа (предустановочный, постустановочный, хук удаления). Сборка манифеста завершится ошибкой, если будет обнаружено более одного хука любого типа. ``` ┌─────────────────────────────────────────────────────────────┐ │ install flow │ │ │ │ upload package → [pre-install] → metadata migration → │ │ generate SDK → [post-install] │ │ │ │ old schema visible new schema visible │ └─────────────────────────────────────────────────────────────┘ ``` ## Краткий обзор | | `definePreInstallLogicFunction` | `definePostInstallLogicFunction` | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | | Запуски | До миграции метаданных — **предыдущая** схема и данные остаются нетронутыми | После миграции и генерации SDK — **новая** схема уже применяется | | Выполнение | Всегда синхронное; блокирует установку | Асинхронно по умолчанию (постановка в очередь, 3 повторные попытки); синхронный режим по желанию через `shouldRunSynchronously: true` | | При ошибке | Установка **прерывается** до любых изменений схемы | Асинхронный режим: до 3 повторных попыток. Синхронный режим: вызывающая сторона получает `POST_INSTALL_ERROR` (изменения схемы **не** откатываются) | | Типичное использование | Создать резервную копию или исправить данные, которые были бы потеряны при миграции; отклонить рискованное обновление, выбросив исключение | Наполнить данными по умолчанию, настроить рабочее пространство, зарегистрировать внешние ресурсы | **Общее практическое правило:** по умолчанию используйте post-install. Обращайтесь к pre-install только тогда, когда сама миграция разрушительна и вам нужно перехватить предыдущее состояние, прежде чем оно исчезнет. | Вы хотите... | Использовать | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | Инициализировать данные, настроить рабочее пространство, зарегистрировать внешние ресурсы | `post-install` | | Длительные операции, которые не должны блокировать ответ установки | `post-install` (асинхронный режим по умолчанию, с повторными попытками воркера) | | Быстрая настройка, на которую вызывающая сторона полагается сразу после завершения установки | `post-install` с `shouldRunSynchronously: true` | | Прочитать или сохранить данные, которые предстоящая миграция может потерять | `pre-install` | | Отклонить обновление, которое повредит существующие данные | `pre-install` (бросьте исключение из обработчика) | | Согласование при каждом обновлении | Любой хук с `shouldRunOnVersionUpgrade: true` | ## Общее поведение для обоих хуков * Конфиг — это конфиг `defineLogicFunction` без настроек триггера, но с `shouldRunOnVersionUpgrade`. * **Когда запускается**: по умолчанию только при чистой установке. Установите `shouldRunOnVersionUpgrade: true`, чтобы также запускать его при обновлениях. Используйте `previousVersion` / `newVersion`, чтобы разветвлять логику в зависимости от пути обновления. * **Идемпотентность имеет значение**: асинхронный post-install может быть повторно выполнен, и любой хук будет запускаться повторно при обновлениях, если включён `shouldRunOnVersionUpgrade`. * Обычное окружение logic-функций (`APPLICATION_ID`, `APP_ACCESS_TOKEN`, `API_URL`) инъецируется, поэтому вы можете вызывать Twenty API с токеном своего приложения. * Хук автоматически прикрепляется к манифесту приложения на этапе сборки (`preInstallLogicFunction` / `postInstallLogicFunction`) — ничего не нужно указывать в [`defineApplication()`](/l/ru/developers/extend/apps/config/application). * Значение `timeoutSeconds` по умолчанию — 300, чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных. * **Не выполняется в dev-режиме**: `yarn twenty dev` пропускает процесс установки и синхронизирует файлы напрямую, поэтому хуки там никогда не запускаются. Вместо этого запускайте их вручную: ```bash filename="Terminal" yarn twenty dev:function:exec --postInstall yarn twenty dev:function:exec --preInstall ``` Запускается после завершения установки приложения: метаданные синхронизированы, SDK-клиент сгенерирован, новая схема доступна для запросов. Пример — инициализировать запись по умолчанию при новой установке: ```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 => { 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, }); ``` Флаг `shouldRunSynchronously` управляет моделью выполнения: * `false` *(по умолчанию)* — ставится в очередь сообщений (`retryLimit: 3`) и выполняется воркером. Ответ установки возвращается, как только задача поставлена в очередь. **Используйте для длительных операций** — инициализация больших наборов данных, медленные сторонние API. * `true` — выполняется непосредственно в процессе установки. Запрос на установку блокируется до завершения обработчика; выброшенная ошибка возвращается вызывающей стороне как `POST_INSTALL_ERROR` (без повторных попыток). **Используйте для быстрых операций, которые обязательно должны завершиться до ответа.** На этом этапе миграция уже применена, поэтому сбой не откатывает изменения схемы — он только сообщает об ошибке. Запускается до миграции метаданных, в контексте **предыдущей** схемы — подходящее место, чтобы создать резервную копию данных, которые были бы потеряны при миграции, или отклонить рискованное обновление. Перед выполнением сервер запускает чисто добавочную «урезанную синхронизацию», которая регистрирует только pre-install функцию новой версии; всё остальное — объекты, поля и данные предыдущей версии — остаётся нетронутым во время выполнения вашего обработчика. Pre-install всегда **синхронный** и блокирует установку. Если обработчик генерирует исключение, установка прерывается до применения каких-либо изменений схемы — рабочее пространство остаётся на предыдущей версии в согласованном состоянии. Это сделано намеренно: pre-install — ваш последний шанс отказать в рискованном обновлении. Пример — скопировать значения устаревшего поля перед тем, как миграция его удалит: ```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 => { // 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, }); ``` ## Хук удаления `defineUninstallLogicFunction` объявляет хук, который выполняется, когда пользователь удаляет ваше приложение. Он выполняется **до того**, как метаданные, данные и код приложения будут удалены — после запуска миграции удаления уже нечему будет выполняться — поэтому ваш обработчик все еще может запрашивать объекты и записи приложения. Используйте его для очистки внешних ресурсов: отмены выделения ресурсов API, удаления оставшихся ботов, отзыва вебхуков. Заметки: * Хук работает по принципу «по возможности»: он выполняется синхронно, но сбой только логируется и **никогда не блокирует удаление** — очистка не должна делать удаление приложения невозможным. * Он получает `UninstallPayload` (`{ version?: string }` — версия, которая удаляется). * Он **не** выполняется при откате неуспешной новой установки — приложение так и не было полностью установлено. * Хук не может выполниться после того, как приложение удалено, поэтому внешняя очистка, которая зависит от данных приложения (например, ID ботов, сохраненные в записях), должна выполняться здесь, а не во внешнем запланированном задании. * Как и установочные хуки, он **не выполняется в режиме разработки** — вместо этого запустите его вручную: ```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 => { 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, }); ```