mcpField
C
codegen-over-complex-types

codegen-over-complex-types

github.com/marius-townhouse开发645 次安装39 周前更新
代码仓库

关于 codegen-over-complex-types

一项 TypeScript 开发技能,说明何时应从模式生成类型,而非编写复杂的类型级代码。内容涵盖 OpenAPI、GraphQL 和数据库模式,以及保持生成类型同步的方法。

主要功能

  1. 01评估复杂类型是否适合代码生成
  2. 02从 OpenAPI 和 GraphQL 生成类型
  3. 03从数据库模式生成类型
  4. 04将生成步骤纳入构建工作流
  5. 05组合生成类型与应用专用类型

适用场景

  1. 01在 TypeScript 中映射外部 API 模式
  2. 02减少复杂的类型级逻辑
  3. 03模式变更后重新生成类型

安装

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill codegen-over-complex-types

安装后在 Claude Code 中输入/codegen-over-complex-types确认已加载。

详细介绍

将代码生成视为复杂类型的替代方案

概述

有时,最好的类型级代码就是完全不写类型级代码。当类型变得极其复杂、需要映射外部架构(API、数据库、协议),或必须与不断变化的外部来源保持同步时,代码生成通常比复杂的类型级编程更合适。

代码生成将编译时复杂性转移到构建时生成,通常能产出更简单、更易维护的代码,并使其与事实来源保持同步。

何时使用此技能

  • 类型映射外部架构(OpenAPI、GraphQL、数据库)
  • 类型级逻辑变得极其复杂
  • 类型需要与外部来源保持同步
  • 类型维护成本超过其价值
  • 团队难以应对复杂的类型级代码

铁律

当类型变得过于复杂,或必须与外部来源保持同步时,应根据架构生成类型,而不是编写复杂的类型级代码。

检测

留意以下信号:

// RED FLAGS - Complex types that might be generated
// 50+ lines of conditional types to parse a URL
type ParseURL<T> = /* extremely complex type-level parser */;

// Types manually maintained to match API
type APIResponse = {
  // 100+ fields that must match backend
  // Every API change requires manual updates
};

// Types derived from JSON Schema via complex mappings
type FromSchema<T> = /* recursive conditional mapped type */;

复杂性权衡

// OPTION 1: Complex type-level code (maintainability cost)
type ParseOpenAPI<Schema> = Schema extends {
  paths: infer Paths
} ? {
  [Path in keyof Paths]: Paths[Path] extends {
    [Method in 'get' | 'post' | 'put' | 'delete']: {
      responses: infer Responses
    }
  } ? {
    [Method in keyof Paths[Path]]: Responses extends {
      200: { content: { 'application/json': infer Body } }
    } ? Body : never
  } : never
} : never;
// 50+ more lines of type-level logic...

// OPTION 2: Generated types (build-time cost)
// Generated from OpenAPI schema:
interface GetUserResponse { /* ... */ }
interface CreateUserRequest { /* ... */ }
// Clear, debuggable, always in sync

从 OpenAPI 生成

# Generate TypeScript from OpenAPI schema
npm install -D openapi-typescript
npx openapi-typescript schema.yaml -o src/api-types.ts
// Generated types - always in sync with API
export interface paths {
  "/users": {
    get: {
      responses: {
        200: {
          content: {
            "application/json": components["schemas"]["UserList"];
          };
        };
      };
    };
    post: {
      requestBody: {
        content: {
          "application/json": components["schemas"]["CreateUserRequest"];
        };
      };
    };
  };
}

export interface components {
  schemas: {
    User: {
      id: string;
      name: string;
      email: string;
    };
    UserList: {
      users: components["schemas"]["User"][];
      total: number;
    };
  };
}

从 GraphQL 生成

# Generate TypeScript from GraphQL schema
npm install -D @graphql-codegen/cli @graphql-codegen/typescript
# codegen.yml
schema: schema.graphql
generates:
  src/generated/graphql.ts:
    plugins:
      - typescript
      - typescript-operations
// Generated types from GraphQL schema
export type User = {
  __typename?: 'User';
  id: Scalars['ID']['output'];
  name: Scalars['String']['output'];
  email: Scalars['String']['output'];
  posts?: Maybe<Array<Maybe<Post>>>;
};

export type GetUserQueryVariables = {
  id: Scalars['ID']['input'];
};

export type GetUserQuery = {
  __typename?: 'Query';
  user?: {
    __typename?: 'User';
    id: string;
    name: string;
  } | null;
};

从数据库架构生成

# Generate TypeScript from database
npm install -D prisma
npx prisma generate
// Generated from database schema
export type User = {
  id: string
  email: string
  name: string | null
  posts: Post[]
}

export type Post = {
  id: string
  title: string
  content: string | null
  published: boolean
  author: User
  authorId: string
}

何时选择代码生成

在以下情况下选择代码生成:

// 1. Source of truth is external
// API schema, database, protocol buffer definition
// → Generate types, don't write them

// 2. Types would be extremely complex
// Type-level URL parser, complex state machines
// → Generate simple types instead

// 3. Synchronization is critical
// API changes must be reflected in types
// → CI generates types from schema

// 4. Team type-level expertise is limited
// Complex type code is hard to maintain
// → Generated code is easier to understand

何时选择类型级代码

在以下情况下选择类型级编程:

// 1. Deriving from existing TypeScript types
// Deriving variants, transformations of your own types
// → Type-level code is appropriate

// 2. Simple transformations
// Pick, Omit, Partial - standard utilities
// → Type-level is simpler than codegen

// 3. No external source of truth
// Internal domain models
// → Write types directly

// 4. Need runtime flexibility
// Types depend on runtime values
// → Type-level code can handle this

保持生成的类型同步

// package.json
{
  "scripts": {
    "generate:api": "openapi-typescript api.yaml -o src/api-types.ts",
    "generate:db": "prisma generate",
    "build": "npm run generate:api && npm run generate:db && tsc",
    "predev": "npm run generate:api && npm run generate:db"
  }
}
# .github/workflows/ci.yml
- name: Check generated types are up to date
  run: |
    npm run generate:api
    git diff --exit-code src/api-types.ts

混合方案

// Generate base types from external source
import type { User as GeneratedUser } from './generated/api';

// Extend with application-specific types
interface User extends GeneratedUser {
  // Add computed properties
  displayName: string;
}

// Derive using type-level code
type UserInput = Omit<User, 'id' | 'createdAt'>;
type UserUpdate = Partial<UserInput>;

抵御压力的决策流程

在类型级代码和代码生成之间做出选择时:

  1. 评估复杂性:类型级代码是否易于维护?
  2. 检查事实来源:是否存在外部架构?
  3. 考虑团队情况:团队能否维护复杂的类型代码?
  4. 评估偏差风险:类型需要多久更新一次?
  5. 分别进行原型验证:尝试两种方案,比较其可维护性

警示信号

症状问题解决方案
类型定义超过 100 行过于复杂根据架构生成
API 变更需要手动更新存在偏差风险根据 API 自动生成
团队成员不愿修改类型文件过于复杂简化或生成
类型与后端不同步缺少单一事实来源根据架构生成

常见的自我说服

“我不想增加构建步骤”

实际情况:现代开发本就包含构建步骤。类型生成速度很快,也能融入现有工作流。

“生成的代码很难看”

实际情况:你不需要阅读生成的代码,只需使用它。生成的类型简洁且类型定义完善。

“我手写的类型会更好”

实际情况:刚开始时,你或许能写出更漂亮的类型,但生成的类型会自动保持同步。

快速参考

来源工具命令
OpenAPIopenapi-typescriptnpx openapi-typescript schema.yaml -o types.ts
GraphQL@graphql-codegennpx graphql-codegen
数据库Prismanpx prisma generate
JSON Schemajson-schema-to-typescriptnpx json2ts schema.json -o types.ts
Protobufprotobuf-tsnpx protoc --ts_out

总结

当类型变得过于复杂,或必须与外部来源保持同步时,就生成它们。代码生成以构建复杂性换取可维护性和正确性。

参考资料

  • Dan Vanderkam 著《Effective TypeScript》第 2 版
  • 条目 58:将代码生成视为复杂类型的替代方案

评论

支持 Markdown