--- title: 安装钩子 description: 在安装之前或之后运行逻辑——预置数据、备份记录、验证升级。 icon: wrench --- 安装钩子是在安装或升级生命周期期间运行的特殊逻辑函数。 它们与常规的[逻辑函数](/l/zh/developers/extend/apps/logic/logic-functions)共享相同的处理程序运行时,并接收一个 `InstallPayload`(`{ previousVersion?: string; newVersion: string }`——在全新安装时 `previousVersion` 为 `undefined`),但它们使用自己的 define 函数声明,并且存在于普通触发模型(HTTP、cron、数据库事件)之外。 每个应用**最多只能定义一个安装前函数**和**最多一个安装后函数**。 如果检测到任一类型多于一个,清单构建将报错。 ``` ┌─────────────────────────────────────────────────────────────┐ │ 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。 仅当迁移本身具有破坏性,且你需要在其丢失之前拦截先前状态时,才使用安装前。 | 你想要... | 使用 | | ------------------ | ------------------------------------------------ | | 预填充数据、配置工作区、注册外部资源 | `post-install` | | 不应阻塞安装响应的长时间运行任务 | `post-install`(默认异步模式,带工作线程重试) | | 安装返回后调用方会立即依赖的快速设置 | `post-install`,配合 `shouldRunSynchronously: true` | | 读取或备份即将被迁移丢失的数据 | `pre-install` | | 拒绝会损坏现有数据的升级 | `pre-install`(从处理程序中抛出异常) | | 在每次升级时执行对账 | 任一钩子配合 `shouldRunOnVersionUpgrade: true` | ## 两个钩子共享的行为 * 该配置等同于 `defineLogicFunction` 的配置减去触发器设置,再加上 `shouldRunOnVersionUpgrade`。 * **运行时机**:默认情况下,仅在全新安装时运行。 将 `shouldRunOnVersionUpgrade: true` 设为 true 以便在升级时也运行。 使用 `previousVersion` / `newVersion` 按升级路径分支处理。 * **幂等性很重要**:异步 post-install 可能会被重试,而且当开启 `shouldRunOnVersionUpgrade` 时,任一钩子都会在升级时重新运行。 * 会注入常规的逻辑函数环境(`APPLICATION_ID`、`APP_ACCESS_TOKEN`、`API_URL`),因此你可以使用应用的令牌调用 Twenty API。 * 该钩子会在构建时自动附加到应用清单上(`preInstallLogicFunction` / `postInstallLogicFunction`)——在 [`defineApplication()`](/l/zh/developers/extend/apps/config/application) 中无需额外引用。 * 默认的 `timeoutSeconds` 为 300,以便支持更长的设置任务,例如数据填充。 * **在开发模式下不会执行**:`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 函数;当你的处理程序运行时,其他一切——上一版本的对象、字段和数据——都不会被触及。 安装前始终为**同步**,并会阻塞安装。 如果处理程序抛出异常,安装会在任何模式更改之前被中止——工作区将保持在上一版本且处于一致状态。 这是有意为之:安装前是你拒绝高风险升级的最后机会。 示例——在迁移删除旧字段之前复制该旧字段的值: ```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, }); ```