openapi-types

📁 manastalukdar/claude-devstudio 📅 5 days ago
3
总安装量
2
周安装量
#58387
全站排名
安装命令
npx skills add https://github.com/manastalukdar/claude-devstudio --skill openapi-types

Agent 安装分布

opencode 2
gemini-cli 2
claude-code 2
github-copilot 2
codex 2
kimi-cli 2

Skill 文档

OpenAPI Type Generator

I’ll generate TypeScript types, client SDKs, and Zod schemas from OpenAPI 3.0 specifications.

Arguments: $ARGUMENTS – path to OpenAPI spec file

Features:

  • TypeScript types from OpenAPI schemas
  • Type-safe fetch/axios client SDK
  • Zod schemas for runtime validation
  • React hooks for data fetching
  • Integration with /api-docs-generate

Token Optimization

This skill uses code generation-specific patterns to minimize token usage:

1. OpenAPI Spec Caching (800 token savings)

Pattern: Cache parsed OpenAPI specification structure

  • Store spec analysis in .openapi-types-cache (1 hour TTL)
  • Cache: schemas, endpoints, parameters, responses
  • Read cached spec on subsequent runs (50 tokens vs 850 tokens fresh)
  • Invalidate on spec file changes
  • Savings: 94% on repeat type generations

2. Bash-Based OpenAPI Validation (600 token savings)

Pattern: Use openapi-generator-cli or swagger-cli for validation

  • Run swagger-cli validate spec.yaml (200 tokens)
  • Parse validation errors with grep
  • No Task agents for spec validation
  • Savings: 75% vs LLM-based validation

3. Template-Based Type Generation (2,000 token savings)

Pattern: Use openapi-typescript or openapi-generator templates

  • Standard tool: openapi-typescript spec.yaml -o types.ts (300 tokens)
  • Pre-defined type generation templates
  • No creative type generation logic needed
  • Savings: 87% vs LLM-generated TypeScript types

4. Early Exit for Current Types (95% savings)

Pattern: Detect if types already generated and current

  • Check for existing types file matching spec name (50 tokens)
  • Compare spec mtime with types file mtime
  • If types current: return types location (100 tokens)
  • Distribution: ~50% of runs check existing types
  • Savings: 100 vs 2,500 tokens for type regeneration checks

5. Sample-Based Schema Analysis (700 token savings)

Pattern: Analyze first 20 schemas for patterns

  • Identify common patterns: pagination, errors, IDs (400 tokens)
  • Apply patterns to remaining schemas
  • Full analysis only if explicitly requested
  • Savings: 65% vs analyzing every schema definition

6. Incremental Type Updates (1,000 token savings)

Pattern: Generate only changed schemas

  • Compare new spec with cached version
  • Generate types only for modified schemas
  • Merge with existing types file
  • Savings: 75% vs full type regeneration

7. Cached Tool Detection (400 token savings)

Pattern: Cache openapi-typescript installation status

  • Check tool installation once, cache result
  • Don’t re-check npm ls on each run
  • Standard installation instructions
  • Savings: 80% on tool detection

8. Bash-Based Code Generation (800 token savings)

Pattern: Use openapi-typescript tool directly

  • Generate all types with single command (300 tokens)
  • Post-process with sed/awk if needed (100 tokens)
  • No Task agents for code generation
  • Savings: 75% vs Task-based generation

Real-World Token Usage Distribution

Typical operation patterns:

  • Check existing types (current): 100 tokens
  • Generate types (first time): 2,500 tokens
  • Update types (spec changed): 1,500 tokens
  • Regenerate (tool changed): 2,000 tokens
  • Incremental update (few schemas): 800 tokens
  • Most common: Check existing types or tool-based generation

Expected per-generation: 2,000-3,000 tokens (50% reduction from 4,000-6,000 baseline) Real-world average: 900 tokens (due to cached specs, early exit, tool-based generation)

Phase 1: OpenAPI Spec Detection

#!/bin/bash
# Detect OpenAPI specifications

echo "=== Detecting OpenAPI Specifications ==="
echo ""

# Find OpenAPI spec files
find_openapi_specs() {
    find . -type f \( \
        -name "openapi*.yaml" -o \
        -name "openapi*.yml" -o \
        -name "swagger*.json" -o \
        -name "swagger*.yaml" -o \
        -name "api-spec*.yaml" \
    \) ! -path "*/node_modules/*" 2>/dev/null
}

SPECS=$(find_openapi_specs)

if [ -z "$SPECS" ]; then
    echo "❌ No OpenAPI specifications found"
    echo ""
    echo "Looking for files like:"
    echo "  - openapi.yaml"
    echo "  - swagger.json"
    echo "  - docs/api-spec.yaml"
    echo ""
    echo "💡 Generate OpenAPI spec with: claude \"/api-docs-generate\""
    exit 1
fi

echo "✓ Found OpenAPI specifications:"
echo "$SPECS" | sed 's/^/  /'
echo ""

# Select spec file
if [ -n "$1" ]; then
    SPEC_FILE="$1"
else
    SPEC_FILE=$(echo "$SPECS" | head -1)
fi

if [ ! -f "$SPEC_FILE" ]; then
    echo "❌ Spec file not found: $SPEC_FILE"
    exit 1
fi

echo "Using spec: $SPEC_FILE"

# Validate OpenAPI spec
validate_spec() {
    echo ""
    echo "=== Validating OpenAPI Spec ==="
    echo ""

    if command -v swagger-cli &> /dev/null; then
        npx swagger-cli validate "$SPEC_FILE"
        echo "✓ Spec is valid"
    else
        echo "⚠️  swagger-cli not found, skipping validation"
        echo "   Install: npm install -g @apidevtools/swagger-cli"
    fi
}

validate_spec

Phase 2: Generate TypeScript Types

// Generated TypeScript types from OpenAPI spec
// Auto-generated - do not edit manually

/**
 * OpenAPI spec: ${SPEC_FILE}
 * Generated: ${DATE}
 */

export namespace API {
  /**
   * Common types
   */
  export type UUID = string;
  export type ISODate = string;
  export type Email = string;

  /**
   * User schema
   */
  export interface User {
    /** Unique user identifier */
    id: UUID;

    /** User's email address */
    email: Email;

    /** User's display name */
    name: string;

    /** User role for authorization */
    role: 'admin' | 'user' | 'guest';

    /** Account creation timestamp */
    createdAt: ISODate;

    /** Last update timestamp */
    updatedAt: ISODate;

    /** Optional user profile */
    profile?: UserProfile;
  }

  /**
   * User profile schema
   */
  export interface UserProfile {
    /** Profile bio */
    bio?: string;

    /** Avatar image URL */
    avatarUrl?: string;

    /** User location */
    location?: string;

    /** Social media links */
    socialLinks?: {
      twitter?: string;
      github?: string;
      linkedin?: string;
    };
  }

  /**
   * Request schemas
   */
  export interface CreateUserRequest {
    email: Email;
    name: string;
    password: string;
    role?: 'user' | 'guest';
  }

  export interface UpdateUserRequest {
    name?: string;
    profile?: Partial<UserProfile>;
  }

  export interface LoginRequest {
    email: Email;
    password: string;
  }

  /**
   * Response schemas
   */
  export interface UserResponse {
    data: User;
    message?: string;
  }

  export interface UsersListResponse {
    data: User[];
    pagination: PaginationMeta;
  }

  export interface LoginResponse {
    token: string;
    user: User;
    expiresIn: number;
  }

  export interface ErrorResponse {
    error: {
      code: string;
      message: string;
      details?: Record<string, unknown>;
    };
    timestamp: ISODate;
  }

  /**
   * Pagination metadata
   */
  export interface PaginationMeta {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
    hasNext: boolean;
    hasPrevious: boolean;
  }

  /**
   * Query parameters
   */
  export interface ListUsersParams {
    page?: number;
    limit?: number;
    search?: string;
    sortBy?: 'name' | 'createdAt' | 'updatedAt';
    sortOrder?: 'asc' | 'desc';
  }

  /**
   * Path parameters
   */
  export interface UserIdParam {
    userId: UUID;
  }

  /**
   * API endpoints
   */
  export namespace Endpoints {
    export const USERS_LIST = '/api/users';
    export const USER_GET = '/api/users/:userId';
    export const USER_CREATE = '/api/users';
    export const USER_UPDATE = '/api/users/:userId';
    export const USER_DELETE = '/api/users/:userId';
    export const AUTH_LOGIN = '/api/auth/login';
    export const AUTH_LOGOUT = '/api/auth/logout';
  }
}

Phase 3: Generate Zod Schemas

// Zod schemas for runtime validation
import { z } from 'zod';

/**
 * User schema
 */
export const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  name: z.string().min(1).max(255),
  role: z.enum(['admin', 'user', 'guest']),
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
  profile: z
    .object({
      bio: z.string().optional(),
      avatarUrl: z.string().url().optional(),
      location: z.string().optional(),
      socialLinks: z
        .object({
          twitter: z.string().url().optional(),
          github: z.string().url().optional(),
          linkedin: z.string().url().optional(),
        })
        .optional(),
    })
    .optional(),
});

export type User = z.infer<typeof UserSchema>;

/**
 * Request schemas
 */
export const CreateUserRequestSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(255),
  password: z.string().min(8).max(255),
  role: z.enum(['user', 'guest']).optional(),
});

export type CreateUserRequest = z.infer<typeof CreateUserRequestSchema>;

export const UpdateUserRequestSchema = z.object({
  name: z.string().min(1).max(255).optional(),
  profile: z
    .object({
      bio: z.string().optional(),
      avatarUrl: z.string().url().optional(),
      location: z.string().optional(),
    })
    .partial()
    .optional(),
});

export type UpdateUserRequest = z.infer<typeof UpdateUserRequestSchema>;

export const LoginRequestSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1),
});

export type LoginRequest = z.infer<typeof LoginRequestSchema>;

/**
 * Response schemas
 */
export const UserResponseSchema = z.object({
  data: UserSchema,
  message: z.string().optional(),
});

export type UserResponse = z.infer<typeof UserResponseSchema>;

export const UsersListResponseSchema = z.object({
  data: z.array(UserSchema),
  pagination: z.object({
    page: z.number().int().positive(),
    pageSize: z.number().int().positive(),
    total: z.number().int().nonnegative(),
    totalPages: z.number().int().nonnegative(),
    hasNext: z.boolean(),
    hasPrevious: z.boolean(),
  }),
});

export type UsersListResponse = z.infer<typeof UsersListResponseSchema>;

export const ErrorResponseSchema = z.object({
  error: z.object({
    code: z.string(),
    message: z.string(),
    details: z.record(z.unknown()).optional(),
  }),
  timestamp: z.string().datetime(),
});

export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;

/**
 * Validation utilities
 */
export function validateCreateUserRequest(
  data: unknown
): CreateUserRequest {
  return CreateUserRequestSchema.parse(data);
}

export function validateUserResponse(data: unknown): UserResponse {
  return UserResponseSchema.parse(data);
}

export function isValidUser(data: unknown): data is User {
  return UserSchema.safeParse(data).success;
}

Phase 4: Generate Fetch Client SDK

// Type-safe fetch client SDK
import type { API } from './types';

/**
 * API client configuration
 */
export interface ClientConfig {
  baseURL: string;
  headers?: Record<string, string>;
  timeout?: number;
}

/**
 * API client error
 */
export class APIError extends Error {
  constructor(
    public status: number,
    public code: string,
    message: string,
    public details?: unknown
  ) {
    super(message);
    this.name = 'APIError';
  }
}

/**
 * Type-safe API client
 */
export class APIClient {
  private baseURL: string;
  private headers: Record<string, string>;
  private timeout: number;

  constructor(config: ClientConfig) {
    this.baseURL = config.baseURL.replace(/\/$/, '');
    this.headers = {
      'Content-Type': 'application/json',
      ...config.headers,
    };
    this.timeout = config.timeout || 30000;
  }

  /**
   * Set authorization token
   */
  setAuthToken(token: string): void {
    this.headers['Authorization'] = `Bearer ${token}`;
  }

  /**
   * Clear authorization token
   */
  clearAuthToken(): void {
    delete this.headers['Authorization'];
  }

  /**
   * Make HTTP request
   */
  private async request<T>(
    method: string,
    path: string,
    options?: {
      params?: Record<string, any>;
      body?: unknown;
      headers?: Record<string, string>;
    }
  ): Promise<T> {
    // Build URL with query parameters
    const url = new URL(`${this.baseURL}${path}`);
    if (options?.params) {
      Object.entries(options.params).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          url.searchParams.append(key, String(value));
        }
      });
    }

    // Make request with timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);

    try {
      const response = await fetch(url.toString(), {
        method,
        headers: { ...this.headers, ...options?.headers },
        body: options?.body ? JSON.stringify(options.body) : undefined,
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      // Handle error responses
      if (!response.ok) {
        const error = await response.json().catch(() => ({}));
        throw new APIError(
          response.status,
          error.error?.code || 'UNKNOWN_ERROR',
          error.error?.message || response.statusText,
          error.error?.details
        );
      }

      // Handle 204 No Content
      if (response.status === 204) {
        return undefined as T;
      }

      return response.json();
    } catch (error) {
      clearTimeout(timeoutId);

      if (error instanceof APIError) {
        throw error;
      }

      if (error instanceof Error && error.name === 'AbortError') {
        throw new APIError(408, 'TIMEOUT', 'Request timeout');
      }

      throw new APIError(
        0,
        'NETWORK_ERROR',
        error instanceof Error ? error.message : 'Network error'
      );
    }
  }

  /**
   * Users API
   */
  readonly users = {
    /**
     * List users
     */
    list: (params?: API.ListUsersParams) =>
      this.request<API.UsersListResponse>('GET', '/api/users', { params }),

    /**
     * Get user by ID
     */
    get: (userId: string) =>
      this.request<API.UserResponse>('GET', `/api/users/${userId}`),

    /**
     * Create new user
     */
    create: (data: API.CreateUserRequest) =>
      this.request<API.UserResponse>('POST', '/api/users', { body: data }),

    /**
     * Update user
     */
    update: (userId: string, data: API.UpdateUserRequest) =>
      this.request<API.UserResponse>('PUT', `/api/users/${userId}`, {
        body: data,
      }),

    /**
     * Delete user
     */
    delete: (userId: string) =>
      this.request<void>('DELETE', `/api/users/${userId}`),
  };

  /**
   * Authentication API
   */
  readonly auth = {
    /**
     * Login
     */
    login: (data: API.LoginRequest) =>
      this.request<API.LoginResponse>('POST', '/api/auth/login', {
        body: data,
      }),

    /**
     * Logout
     */
    logout: () => this.request<void>('POST', '/api/auth/logout'),
  };
}

/**
 * Create API client instance
 */
export function createAPIClient(config: ClientConfig): APIClient {
  return new APIClient(config);
}

/**
 * Default client instance
 */
export const apiClient = createAPIClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
});

Phase 5: Generate React Hooks

// React hooks for data fetching
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import type { API } from './types';
import { apiClient } from './client';

/**
 * Hook to list users
 */
export function useUsers(params?: API.ListUsersParams) {
  return useQuery({
    queryKey: ['users', params],
    queryFn: () => apiClient.users.list(params),
  });
}

/**
 * Hook to get user by ID
 */
export function useUser(userId: string) {
  return useQuery({
    queryKey: ['users', userId],
    queryFn: () => apiClient.users.get(userId),
    enabled: !!userId,
  });
}

/**
 * Hook to create user
 */
export function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: API.CreateUserRequest) =>
      apiClient.users.create(data),
    onSuccess: () => {
      // Invalidate users list
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

/**
 * Hook to update user
 */
export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({
      userId,
      data,
    }: {
      userId: string;
      data: API.UpdateUserRequest;
    }) => apiClient.users.update(userId, data),
    onSuccess: (_, variables) => {
      // Invalidate specific user and users list
      queryClient.invalidateQueries({ queryKey: ['users', variables.userId] });
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

/**
 * Hook to delete user
 */
export function useDeleteUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (userId: string) => apiClient.users.delete(userId),
    onSuccess: () => {
      // Invalidate users list
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

/**
 * Hook to login
 */
export function useLogin() {
  return useMutation({
    mutationFn: (data: API.LoginRequest) => apiClient.auth.login(data),
    onSuccess: (response) => {
      // Store auth token
      apiClient.setAuthToken(response.token);
      localStorage.setItem('auth_token', response.token);
    },
  });
}

/**
 * Hook to logout
 */
export function useLogout() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: () => apiClient.auth.logout(),
    onSuccess: () => {
      // Clear auth token
      apiClient.clearAuthToken();
      localStorage.removeItem('auth_token');

      // Clear all queries
      queryClient.clear();
    },
  });
}

Phase 6: Usage Examples

// Example: Using the generated client in a React component
import { useUsers, useCreateUser } from './api/hooks';

export function UsersPage() {
  const { data, isLoading, error } = useUsers({ page: 1, limit: 10 });
  const createUser = useCreateUser();

  const handleCreateUser = async () => {
    try {
      await createUser.mutateAsync({
        email: 'user@example.com',
        name: 'John Doe',
        password: 'secure-password',
      });
      alert('User created!');
    } catch (error) {
      console.error('Failed to create user:', error);
    }
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Users</h1>
      <button onClick={handleCreateUser}>Create User</button>
      <ul>
        {data?.data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
// Example: Using the client directly (non-React)
import { apiClient } from './api/client';

async function fetchUsers() {
  try {
    const response = await apiClient.users.list({ page: 1, limit: 10 });
    console.log('Users:', response.data);
    console.log('Pagination:', response.pagination);
  } catch (error) {
    if (error instanceof APIError) {
      console.error(`API Error (${error.status}):`, error.message);
    } else {
      console.error('Unknown error:', error);
    }
  }
}

Summary

echo ""
echo "=== ✓ OpenAPI Type Generation Complete ==="
echo ""
echo "📁 Generated files:"
echo "  - src/api/types.ts           # TypeScript types"
echo "  - src/api/schemas.ts         # Zod validation schemas"
echo "  - src/api/client.ts          # Fetch client SDK"
echo "  - src/api/hooks.ts           # React hooks"
echo ""
echo "📦 Install dependencies:"
echo "  npm install zod @tanstack/react-query"
echo ""
echo "🚀 Usage:"
echo ""
echo "# In React components:"
echo "import { useUsers, useCreateUser } from './api/hooks';"
echo ""
echo "# Direct client usage:"
echo "import { apiClient } from './api/client';"
echo "const users = await apiClient.users.list();"
echo ""
echo "💡 Integration points:"
echo "  - /api-docs-generate - Generate OpenAPI spec"
echo "  - /api-test-generate - Generate API tests"
echo "  - /types-generate - Additional type generation"

Best Practices

Type Safety:

  • Generate types from single source of truth
  • Use Zod for runtime validation
  • Keep types in sync with API

Client SDK:

  • Type-safe methods
  • Proper error handling
  • Request/response validation

Integration Points:

  • /api-docs-generate – OpenAPI spec generation
  • /api-test-generate – API tests
  • /mock-generate – Mock data

What I’ll Actually Do

  1. Find OpenAPI spec – Detect spec files
  2. Generate types – TypeScript interfaces
  3. Create Zod schemas – Runtime validation
  4. Build client SDK – Type-safe fetch client
  5. Generate hooks – React Query hooks
  6. Add examples – Usage documentation

Important: I will NEVER add AI attribution.

Credits: Based on openapi-typescript, Zod, React Query, and API client patterns from tRPC, GraphQL clients, and REST API best practices.