mcpField
A
adopting-generated-api-types

adopting-generated-api-types

github.com/posthog开发40.5k173 次安装今天更新
代码仓库

关于 adopting-generated-api-types

面向 PostHog 前端开发者的技能,指导将手写 API 调用和类型迁移到 OpenAPI 生成的 TypeScript 函数与类型,并提供兼容性检查和验证步骤。

主要功能

  1. 01识别三类手写 API 调用
  2. 02查找生成的函数和类型
  3. 03检查类型兼容性并迁移调用
  4. 04清理旧类型并运行验证

适用场景

  1. 01替换旧式 API 请求
  2. 02将手写响应类型换为生成类型
  3. 03后端 Schema 改进后的前端整理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/posthog/posthog --skill adopting-generated-api-types

安装后在 Claude Code 中输入/adopting-generated-api-types确认已加载。

附带文件references/migration-patterns.mdreferences/type-compatibility.md

详细介绍

采用生成的 API 类型

概述

PostHog 通过 OpenAPI 流水线从 Django serializers 生成 TypeScript API 客户端函数和类型:

Django serializer → drf-spectacular → OpenAPI JSON → Orval → TypeScript (api.ts + api.schemas.ts + api.zod.ts)

生成的文件位于:

  • 核心: frontend/src/generated/core/api.ts、api.schemas.ts 和 api.zod.ts
  • 产品: products/<product>/frontend/generated/api.ts、api.schemas.ts 和 api.zod.ts

生成的类型使用 Api 后缀(DashboardApi、SurveyApi)。手写类型从不使用该后缀。

本技能指导如何将手动 API 调用和手写类型替换为生成的等价物。

需要迁移的三种手动模式

旧版 frontend/src/lib/api.ts(约 6000 行)包含三层,全部都是迁移目标:

1. 高层对象 API(最常见)

api 对象上特定领域的便捷方法:

api.surveys.get(id)
api.surveys.create(data)
api.dashboards.list()
api.cohorts.update(id, data)
api.actions.create(data)

这是使用最广泛的模式 — 每个实体都有自己的命名空间,包含 CRUD 以及自定义方法(例如 api.surveys.getResponsesCount()、api.dashboards.streamTiles())。

2. 带手动 URL 的原始 HTTP 方法
api.get<SomeType>(`api/projects/${id}/surveys/`)
api.create<SomeType>(`api/projects/${id}/surveys/`, data)
api.update<SomeType>(url, data)
api.put<SomeType>(url, data)
api.delete(url)
3. ApiRequest 构建器(流式 URL 构造)
const url = new ApiRequest().surveys().assembleFullUrl()
const response = await api.get(url)

// or directly:
await new ApiRequest().survey(surveyId).withAction('summarize_responses').create({ data })

在可用的情况下,这三种模式都应替换为生成的函数。

何时使用

  • 修改调用 api.<entity>.<method>() 的文件(例如 api.surveys.get())
  • 修改调用 api.get<T>(...)、api.create<T>(...) 等的文件
  • 修改使用 new ApiRequest() 构造 URL 的文件
  • 修改从 ~/types 导入手写接口以表示 API 响应结构的文件
  • 在后端 serializer 改进后清理前端代码

分步工作流

1. 确定手动调用做了什么

查看现有调用并提取:

  • HTTP 方法 — GET、POST、PUT、PATCH、DELETE
  • 实体和操作 — 什么资源,什么操作
  • 类型参数 — 用于响应的手写类型
2. 找到生成的等价物

生成的函数名遵循 {resource}{Action} 约定:

surveysList          — GET    /api/projects/{id}/surveys/
surveysCreate        — POST   /api/projects/{id}/surveys/
surveysRetrieve      — GET    /api/projects/{id}/surveys/{id}/
surveysPartialUpdate — PATCH  /api/projects/{id}/surveys/{id}/
surveysDestroy       — DELETE /api/projects/{id}/surveys/{id}/

搜索位置:

  • 核心端点:frontend/src/generated/core/api.ts
  • 产品端点:products/<product>/frontend/generated/api.ts

搜索策略:

  1. 在生成的 api.ts 文件中 grep 实体名称
  2. 通过 get*Url 辅助函数搜索 — 每个生成的函数上方都有对应的 URL 构建器
  3. 在 api.schemas.ts 中搜索带 Api 后缀的类型名称

如果不存在生成的函数,后端端点可能缺少 @extend_schema 或 @validated_request。先使用 improving-drf-endpoints 技能修复后端,然后运行 hogli build:openapi。

自定义操作(如 api.surveys.summarize_responses())如果后端 @action 缺少 @extend_schema,可能没有生成的等价物。先检查生成的文件;如果缺失,则修复后端。

3. 检查类型兼容性

将手写类型与生成的 Api 类型进行比较。主要差异:

  • readonly 修饰符 — 生成的类型会标记只读字段
  • 可选与必需 — 生成的类型精确反映 required=
  • 可空性 — null 类型是显式的
  • 额外字段 — 生成的类型可能包含手写类型省略的字段

详情参见 type-compatibility.md。

4. 替换调用

详细的替换前后示例参见 migration-patterns.md,涵盖:

  • 高层对象 API(api.surveys.get() → surveysRetrieve())
  • 原始 HTTP 方法(api.get<T>(url) → 生成的函数)
  • ApiRequest 构建器 → 生成的函数
  • 分页列表调用
  • 带请求体的创建/更新
  • 删除调用
  • Kea logic 加载器和监听器
  • 带中止信号的调用
5. 在使用位置替换类型

将下游引用从手写类型更新为生成的类型:

// Before
function renderSurvey(survey: Survey): JSX.Element { ... }

// After
function renderSurvey(survey: SurveyApi): JSX.Element { ... }
6. 清理无用类型

迁移完某个手写类型的所有用法后:

  1. 从 types 或本地文件中移除该类型定义
  2. 移除未使用的导入
  3. 运行 pnpm --filter=@posthog/frontend types:check 验证没有破坏任何内容

决策指南

场景操作
生成的函数存在用生成的函数替换手动调用
生成的类型存在但函数不存在将生成的类型用作手动调用的泛型参数,并提交后续任务以添加 @extend_schema
两者都不存在保留手动模式,先修复后端 serializer/viewset
自定义 action 没有对应的生成等价物保留 api.<>.<method>() 调用,先修复后端的 @action 注解
生成的类型与手写类型结构不同调整调用点以适应生成的类型——serializer 是唯一事实来源
代码会修改响应对象使用本地可变副本:const mutable = { ...response } 并修改它
同时需要读取和写入类型读取使用 FooApi,通过 Parameters<typeof fooCreate>[1] 派生写入类型,或使用 PatchedFooApi

导入约定

// Core generated functions — import from api.ts
import { domainsList, domainsCreate, domainsRetrieve } from '~/generated/core/api'

// Core generated types — import type from api.schemas.ts
import type { OrganizationDomainApi } from '~/generated/core/api.schemas'

// Core generated Zod schemas — import from api.zod.ts
import { DomainsCreateBody } from '~/generated/core/api.zod'

// Product generated functions — NO tilde prefix, use 'products/' path
import { surveysList, surveysRetrieve } from 'products/surveys/frontend/generated/api'
import type { SurveyApi } from 'products/surveys/frontend/generated/api.schemas'
import { SurveysCreateBody } from 'products/surveys/frontend/generated/api.zod'

// Within a product, relative imports also work
import { logsAlertsCreate } from '../generated/api'
import type { LogsAlertConfigurationApi } from '../generated/api.schemas'
import { LogsAlertsCreateBody } from '../generated/api.zod'

路径规则:

  • 核心:~/generated/core/...(波浪号前缀)
  • 外部产品:products/<product>/frontend/generated/...(无波浪号)
  • 内部产品:相对路径 ../generated/... 或 ./generated/...

使用 import type 导入类型以启用正确的 tree-shaking。

生成的函数在底层如何工作

生成的函数通过 api-orval-mutator.ts 包装同一个 api 模块:

surveysList(projectId, params)
  → apiMutator(url, { method: 'GET' })
    → api.get(url)

切换到生成的函数不会改变 HTTP 行为——相同的 cookie、相同的 CSRF、相同的错误处理。唯一的区别是类型安全和 URL 构造。

验证迁移

  1. TypeScript 检查: pnpm --filter=@posthog/frontend types:check
  2. 搜索遗留的手动类型: 在整个代码库中搜索旧类型名
  3. 运行相关测试: hogli test <test>

相关

  • 后端侧: 使用 improving-drf-endpoints 修复产生劣质类型的 serializer
  • 类型系统文档: docs/published/handbook/engineering/type-system
  • API mutator: frontend/src/lib/api-orval-mutator.ts
  • 重新生成类型: hogli build:openapi

评论

支持 Markdown