openapi-types
npx skills add https://github.com/manastalukdar/claude-devstudio --skill openapi-types
Agent 安装分布
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
- Find OpenAPI spec – Detect spec files
- Generate types – TypeScript interfaces
- Create Zod schemas – Runtime validation
- Build client SDK – Type-safe fetch client
- Generate hooks – React Query hooks
- 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.