返回 Skill 列表
extension
分类: 开发与工程无需 API Key

type-generation

从OpenAPI和GraphQL模式自动生成类型。涵盖openapi-typescript、graphql-codegen和合约同步。使用场景:当用户询问“openapi-typescript”、“graphql-codegen”、“从API生成类型”、“类型生成”、“API类型”、“模式到TypeScript”。不适用于:手动类型定义 - 使用TypeScript技能,合约验证 - 使用`openapi-contract`技能

person作者: jakexiaohubgithub

Type Generation - Quick Reference

When NOT to Use This Skill

  • Manual type writing - Use TypeScript skills
  • Contract validation - Use openapi-contract skill
  • GraphQL queries - Use graphql-contract skill

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: typescript for advanced type patterns.

Tool Comparison

| Tool | Source | Output | Use Case | |------|--------|--------|----------| | openapi-typescript | OpenAPI 3.x | TypeScript types | REST APIs | | openapi-fetch | OpenAPI 3.x | Type-safe client | REST + fetch | | graphql-codegen | GraphQL schema | Types + hooks | GraphQL APIs | | orval | OpenAPI 3.x | Types + client | REST + Axios/fetch | | swagger-typescript-api | OpenAPI/Swagger | Types + class client | REST APIs |

openapi-typescript Setup

Installation

npm install -D openapi-typescript

Basic Usage

# From local file
npx openapi-typescript openapi.yaml -o src/api/types.ts

# From URL
npx openapi-typescript https://api.example.com/openapi.json -o src/api/types.ts

# Watch mode
npx openapi-typescript openapi.yaml -o src/api/types.ts --watch

Configuration (openapi-ts.config.ts)

import { defineConfig } from 'openapi-typescript';

export default defineConfig({
  input: './openapi.yaml',
  output: './src/api/types.ts',
  // Alphabetize output
  alphabetize: true,
  // Export type for each path
  exportType: true,
  // Transform property names
  transform: (schemaObject) => {
    // Custom transformation
    return schemaObject;
  },
});

Generated Types Usage

// Generated types
import type { paths, components } from './api/types';

// Request body type
type CreateUserBody = paths['/users']['post']['requestBody']['content']['application/json'];

// Response type
type UserResponse = paths['/users']['get']['responses']['200']['content']['application/json'];

// Schema type
type User = components['schemas']['User'];

// Path parameters
type UserParams = paths['/users/{id}']['parameters']['path'];

openapi-fetch Setup (Type-safe Client)

Installation

npm install openapi-fetch
npm install -D openapi-typescript

Generate Types + Create Client

# Generate types first
npx openapi-typescript openapi.yaml -o src/api/types.ts
// src/api/client.ts
import createClient from 'openapi-fetch';
import type { paths } from './types';

const client = createClient<paths>({
  baseUrl: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json',
  },
});

export default client;

Usage with Type Safety

import client from './api/client';

// GET request - fully typed
const { data, error } = await client.GET('/users/{id}', {
  params: {
    path: { id: '123' },
    query: { include: 'profile' },
  },
});

if (data) {
  // data is properly typed as User
  console.log(data.name);
}

// POST request
const { data: newUser } = await client.POST('/users', {
  body: {
    name: 'John',
    email: 'john@example.com',
  },
});

// With React Query
import { useQuery, useMutation } from '@tanstack/react-query';

function useUser(id: string) {
  return useQuery({
    queryKey: ['user', id],
    queryFn: async () => {
      const { data, error } = await client.GET('/users/{id}', {
        params: { path: { id } },
      });
      if (error) throw error;
      return data;
    },
  });
}

GraphQL Codegen Setup

Installation

npm install -D @graphql-codegen/cli @graphql-codegen/typescript \
  @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-query

Configuration (codegen.ts)

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: 'https://api.example.com/graphql',
  documents: ['src/**/*.graphql', 'src/**/*.tsx'],
  generates: {
    './src/generated/graphql.ts': {
      plugins: [
        'typescript',
        'typescript-operations',
        'typescript-react-query',
      ],
      config: {
        fetcher: {
          endpoint: 'https://api.example.com/graphql',
        },
        exposeQueryKeys: true,
        exposeFetcher: true,
      },
    },
  },
};

export default config;

GraphQL Document

# src/graphql/users.graphql
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
    createdAt
  }
}

mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
    email
  }
}

Generate and Use

npx graphql-codegen
// Auto-generated hooks
import { useGetUserQuery, useCreateUserMutation } from './generated/graphql';

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading } = useGetUserQuery({ id: userId });

  if (isLoading) return <Spinner />;
  return <div>{data?.user?.name}</div>;
}

function CreateUserForm() {
  const mutation = useCreateUserMutation();

  const onSubmit = (data: CreateUserInput) => {
    mutation.mutate({ input: data });
  };

  return <form onSubmit={handleSubmit(onSubmit)}>...</form>;
}

Orval Setup (OpenAPI + Client Generation)

Installation

npm install -D orval

Configuration (orval.config.ts)

import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    input: './openapi.yaml',
    output: {
      target: './src/api/endpoints.ts',
      schemas: './src/api/model',
      client: 'react-query',
      mode: 'tags-split',
      mock: true,
    },
  },
});

Generate

npx orval

Generated Output

// Auto-generated hooks with React Query
import { useGetUsers, useCreateUser } from './api/endpoints';

function UserList() {
  const { data: users } = useGetUsers();
  return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

Package.json Scripts

{
  "scripts": {
    "generate:types": "openapi-typescript openapi.yaml -o src/api/types.ts",
    "generate:client": "orval",
    "generate:graphql": "graphql-codegen",
    "generate": "npm run generate:types && npm run generate:client",
    "predev": "npm run generate",
    "prebuild": "npm run generate"
  }
}

CI/CD Integration

GitHub Actions

name: Type Generation
on: [push, pull_request]

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Generate types
        run: npm run generate:types

      - name: Check for changes
        run: |
          git diff --exit-code src/api/types.ts || \
          (echo "Types are out of date! Run 'npm run generate:types'" && exit 1)

Best Practices

1. Version Control Generated Files

# Option A: Don't commit (regenerate in CI)
src/api/types.ts
src/generated/

# Option B: Commit (recommended for visibility)
# Don't add to .gitignore, commit generated files

2. Pre-commit Hook

// package.json
{
  "lint-staged": {
    "openapi.yaml": [
      "npm run generate:types",
      "git add src/api/types.ts"
    ]
  }
}

3. Watch Mode in Development

{
  "scripts": {
    "dev": "concurrently \"npm run generate:types -- --watch\" \"vite\""
  }
}

4. Type Re-exports

// src/api/index.ts
export type {
  User,
  CreateUserRequest,
  UpdateUserRequest,
  UserListResponse,
} from './types';

export { default as client } from './client';

Common Patterns

Nullable Fields

# OpenAPI
components:
  schemas:
    User:
      properties:
        middleName:
          type: string
          nullable: true
// Generated
interface User {
  middleName: string | null;
}

// Usage
const displayName = user.middleName ?? 'N/A';

Discriminated Unions

# OpenAPI
components:
  schemas:
    Shape:
      oneOf:
        - $ref: '#/components/schemas/Circle'
        - $ref: '#/components/schemas/Rectangle'
      discriminator:
        propertyName: type
// Generated
type Shape = Circle | Rectangle;

// Usage with type narrowing
function getArea(shape: Shape): number {
  switch (shape.type) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
  }
}

Enums

# OpenAPI
components:
  schemas:
    UserRole:
      type: string
      enum: [admin, user, guest]
// Generated as union type
type UserRole = 'admin' | 'user' | 'guest';

// Or as const object
const UserRole = {
  Admin: 'admin',
  User: 'user',
  Guest: 'guest',
} as const;

Anti-Patterns

| Anti-Pattern | Why It's Bad | Correct Approach | |--------------|--------------|------------------| | Manual type sync | Drift, errors | Auto-generate from spec | | any for API responses | No type safety | Use generated types | | Ignoring nullable | Runtime errors | Handle null properly | | No pre-commit generation | Outdated types | Hook into git workflow | | Editing generated files | Lost on regenerate | Extend types separately |

Quick Troubleshooting

| Issue | Likely Cause | Solution | |-------|--------------|----------| | Types don't match API | Outdated spec | Update and regenerate | | Generation fails | Invalid OpenAPI | Validate spec first | | Missing properties | Optional vs required | Check OpenAPI schema | | Wrong enum values | Spec changed | Regenerate types | | Import errors | Path configuration | Check tsconfig paths |

Related Skills