Loading...
Loading...
Use when types become extremely complex. Use when types mirror external schemas. Use when maintaining type-to-schema mappings. Use when types require extensive type-level logic. Use when types drift from data sources.
npx skill4agent add marius-townhouse/effective-typescript-skills codegen-over-complex-types// 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# 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;
};
};
}# 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>;| 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 |
| Source | Tool | Command |
|---|---|---|
| OpenAPI | openapi-typescript | |
| GraphQL | @graphql-codegen | |
| Database | Prisma | |
| JSON Schema | json-schema-to-typescript | |
| Protobuf | protobuf-ts | |