codegen-over-complex-types
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConsider Codegen as an Alternative to Complex Types
考虑用代码生成替代复杂类型
Overview
概述
Sometimes the best type-level code is no type-level code at all. When types become extremely complex, when they mirror external schemas (APIs, databases, protocols), or when they need to stay synchronized with changing external sources, code generation is often a better solution than sophisticated type-level programming.
Code generation trades compile-time complexity for build-time generation, often resulting in simpler, more maintainable code that stays in sync with its source of truth.
有时候最好的类型层级代码就是完全不写类型层级代码。当类型变得极为复杂、类型与外部结构(如API、数据库、协议)一致,或者类型需要与不断变化的外部源保持同步时,代码生成通常比复杂的类型层级编程更合适。
代码生成将编译时的复杂度转换为构建时的生成操作,通常能得到更简洁、更易维护且与真实数据源保持同步的代码。
When to Use This Skill
何时使用该技巧
- Types mirror external schemas (OpenAPI, GraphQL, database)
- Type-level logic becomes extremely complex
- Types need to stay synchronized with external sources
- Type maintenance cost exceeds value
- Team struggles with complex type-level code
- 类型与外部结构(OpenAPI、GraphQL、数据库)一致
- 类型层级逻辑变得极为复杂
- 类型需要与外部源保持同步
- 类型维护成本超过其价值
- 团队难以处理复杂的类型层级代码
The Iron Rule
铁则
When types become too complex or must stay synchronized with external sources, generate them from schemas rather than writing sophisticated type-level code.
当类型过于复杂或必须与外部源保持同步时,从结构生成类型,而非编写复杂的类型层级代码。
Detection
识别信号
Watch for these signals:
typescript
// 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 */;typescript
// 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 */;The Complexity Trade-off
复杂度权衡
typescript
// 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 synctypescript
// 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 syncGenerating from OpenAPI
从OpenAPI生成类型
bash
undefinedbash
undefinedGenerate TypeScript from OpenAPI schema
Generate TypeScript from OpenAPI schema
npm install -D openapi-typescript
npx openapi-typescript schema.yaml -o src/api-types.ts
```typescript
// 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;
};
};
}npm install -D openapi-typescript
npx openapi-typescript schema.yaml -o src/api-types.ts
```typescript
// 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;
};
};
}Generating from GraphQL
从GraphQL生成类型
bash
undefinedbash
undefinedGenerate TypeScript from GraphQL schema
Generate TypeScript from GraphQL schema
npm install -D @graphql-codegen/cli @graphql-codegen/typescript
```yamlnpm install -D @graphql-codegen/cli @graphql-codegen/typescript
```yamlcodegen.yml
codegen.yml
schema: schema.graphql
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-operations
```typescript
// 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;
};schema: schema.graphql
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-operations
```typescript
// 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;
};Generating from Database Schemas
从数据库结构生成类型
bash
undefinedbash
undefinedGenerate TypeScript from database
Generate TypeScript from database
npm install -D prisma
npx prisma generate
```typescript
// 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
}npm install -D prisma
npx prisma generate
```typescript
// 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
}When to Choose Codegen
何时选择代码生成
Choose code generation when:
typescript
// 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选择代码生成的场景:
typescript
// 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 understandWhen to Choose Type-Level Code
何时选择类型层级代码
Choose type-level programming when:
typescript
// 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选择类型层级编程的场景:
typescript
// 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 thisKeeping Generated Types in Sync
保持生成类型的同步
json
// 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"
}
}yaml
undefinedjson
// 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"
}
}yaml
undefined.github/workflows/ci.yml
.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
undefined- name: Check generated types are up to date run: | npm run generate:api git diff --exit-code src/api-types.ts
undefinedHybrid Approach
混合方案
typescript
// 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>;typescript
// 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>;Pressure Resistance Protocol
决策流程
When deciding between type-level code and codegen:
- Assess complexity: Will the type-level code be maintainable?
- Check source of truth: Is there an external schema?
- Consider team: Can the team maintain complex type code?
- Evaluate drift risk: How often will types need updating?
- Prototype both: Try both approaches, compare maintainability
在类型层级代码和代码生成之间做选择时:
- 评估复杂度:类型层级代码是否易于维护?
- 检查真实数据源:是否存在外部结构?
- 考虑团队情况:团队能否维护复杂的类型代码?
- 评估不一致风险:类型需要多久更新一次?
- 两种方案都尝试:分别试用两种方案,对比可维护性
Red Flags
危险信号
| Symptom | Problem | Solution |
|---|---|---|
| 100+ line type definitions | Too complex | Generate from schema |
| Manual updates for API changes | Drift risk | Auto-generate from API |
| Team avoids touching type files | Too complex | Simplify or generate |
| Types out of sync with backend | No single source | Generate from schema |
| 症状 | 问题 | 解决方案 |
|---|---|---|
| 超过100行的类型定义 | 过于复杂 | 从结构生成类型 |
| API变更时需要手动更新类型 | 存在不一致风险 | 从API自动生成类型 |
| 团队避免修改类型文件 | 过于复杂 | 简化或生成类型 |
| 类型与后端不一致 | 缺乏单一真实数据源 | 从结构生成类型 |
Common Rationalizations
常见顾虑
"I don't want a build step"
"我不想增加构建步骤"
Reality: Modern development already has build steps. Type generation is fast and integrates into existing workflows.
实际情况:现代开发流程本来就包含构建步骤。类型生成速度快,且能融入现有工作流。
"Generated code is ugly"
"生成的代码很丑陋"
Reality: You don't read generated code, you use it. The types it produces are clean and well-typed.
实际情况:你不需要阅读生成的代码,只需要使用它。它生成的类型清晰且类型完善。
"I can write better types by hand"
"我手动写的类型更好"
Reality: You might write nicer types initially, but generated types stay in sync automatically.
实际情况:你最初可能写出更优雅的类型,但生成的类型能自动保持同步。
Quick Reference
快速参考
| Source | Tool | Command |
|---|---|---|
| OpenAPI | openapi-typescript | |
| GraphQL | @graphql-codegen | |
| Database | Prisma | |
| JSON Schema | json-schema-to-typescript | |
| Protobuf | protobuf-ts | |
| 来源 | 工具 | 命令 |
|---|---|---|
| OpenAPI | openapi-typescript | |
| GraphQL | @graphql-codegen | |
| 数据库 | Prisma | |
| JSON Schema | json-schema-to-typescript | |
| Protobuf | protobuf-ts | |
The Bottom Line
总结
When types become too complex or must stay synchronized with external sources, generate them. Code generation trades build complexity for maintainability and correctness.
当类型过于复杂或必须与外部源保持同步时,选择生成类型。代码生成以构建时的复杂度换取可维护性和准确性。
Reference
参考资料
- Effective TypeScript, 2nd Edition by Dan Vanderkam
- Item 58: Consider Codegen as an Alternative to Complex Types
- Effective TypeScript, 2nd Edition by Dan Vanderkam
- Item 58: Consider Codegen as an Alternative to Complex Types