breathe-api
Version:
Model Context Protocol server for Breathe HR APIs with Swagger/OpenAPI support - also works with custom APIs
300 lines • 12.3 kB
JavaScript
export async function generateReactNativeClient(parsed, config) {
const files = {};
if (parsed.types) {
files['types.ts'] = parsed.types;
}
files['client.ts'] = generateBaseClient(parsed, config);
files['api.ts'] = generateApiMethods(parsed, config);
if (config.features?.hooks) {
files['hooks.ts'] = generateHooks(parsed, config);
}
if (config.features?.errorHandling) {
files['errors.ts'] = generateErrorHandling();
}
files['index.ts'] = generateIndexFile(config);
return files;
}
function generateBaseClient(_parsed, config) {
const lines = [];
lines.push(`import AsyncStorage from '@react-native-async-storage/async-storage';`);
lines.push(`import { Platform } from 'react-native';`);
lines.push('');
lines.push('export interface ApiClientConfig {');
lines.push(' baseURL: string;');
if (config.authType === 'bearer') {
lines.push(' token?: string;');
}
else if (config.authType === 'api-key') {
lines.push(' apiKey?: string;');
}
lines.push(' headers?: Record<string, string>;');
lines.push(' timeout?: number;');
lines.push('}');
lines.push('');
lines.push('export class ApiClient {');
lines.push(' private config: ApiClientConfig;');
lines.push('');
lines.push(' constructor(config: ApiClientConfig) {');
lines.push(' this.config = {');
lines.push(' timeout: 30000,');
lines.push(' ...config,');
lines.push(' };');
lines.push(' }');
lines.push('');
lines.push(' private async getHeaders(): Promise<Record<string, string>> {');
lines.push(' const headers: Record<string, string> = {');
lines.push(' \'Content-Type\': \'application/json\',');
lines.push(' \'User-Agent\': `ReactNative/${Platform.OS}`,');
lines.push(' ...this.config.headers,');
lines.push(' };');
lines.push('');
if (config.authType === 'bearer') {
lines.push(' const token = this.config.token || await AsyncStorage.getItem(\'authToken\');');
lines.push(' if (token) {');
lines.push(' headers.Authorization = `Bearer ${token}`;');
lines.push(' }');
}
else if (config.authType === 'api-key') {
lines.push(' const apiKey = this.config.apiKey || await AsyncStorage.getItem(\'apiKey\');');
lines.push(' if (apiKey) {');
lines.push(' headers[\'X-API-Key\'] = apiKey;');
lines.push(' }');
}
lines.push('');
lines.push(' return headers;');
lines.push(' }');
lines.push('');
lines.push(' async request<T>(');
lines.push(' method: string,');
lines.push(' path: string,');
lines.push(' options?: {');
lines.push(' params?: Record<string, any>;');
lines.push(' data?: any;');
lines.push(' headers?: Record<string, string>;');
lines.push(' }');
lines.push(' ): Promise<T> {');
lines.push(' const url = new URL(path, this.config.baseURL);');
lines.push('');
lines.push(' if (options?.params) {');
lines.push(' Object.entries(options.params).forEach(([key, value]) => {');
lines.push(' if (value !== undefined && value !== null) {');
lines.push(' url.searchParams.append(key, String(value));');
lines.push(' }');
lines.push(' });');
lines.push(' }');
lines.push('');
lines.push(' const headers = await this.getHeaders();');
lines.push(' const controller = new AbortController();');
lines.push(' const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);');
lines.push('');
lines.push(' try {');
lines.push(' const response = await fetch(url.toString(), {');
lines.push(' method,');
lines.push(' headers: { ...headers, ...options?.headers },');
lines.push(' body: options?.data ? JSON.stringify(options.data) : undefined,');
lines.push(' signal: controller.signal,');
lines.push(' });');
lines.push('');
lines.push(' clearTimeout(timeoutId);');
lines.push('');
lines.push(' if (!response.ok) {');
lines.push(' const error = await response.text();');
lines.push(' throw new Error(`API Error: ${response.status} - ${error}`);');
lines.push(' }');
lines.push('');
lines.push(' const contentType = response.headers.get(\'content-type\');');
lines.push(' if (contentType?.includes(\'application/json\')) {');
lines.push(' return await response.json();');
lines.push(' }');
lines.push('');
lines.push(' return response.text() as unknown as T;');
lines.push(' } catch (error) {');
lines.push(' clearTimeout(timeoutId);');
lines.push(' if (error instanceof Error && error.name === \'AbortError\') {');
lines.push(' throw new Error(\'Request timeout\');');
lines.push(' }');
lines.push(' throw error;');
lines.push(' }');
lines.push(' }');
lines.push('}');
return lines.join('\n');
}
function generateApiMethods(parsed, _config) {
const lines = [];
lines.push(`import { ApiClient } from './client';`);
if (parsed.types) {
lines.push(`import * as Types from './types';`);
}
lines.push('');
lines.push('export class Api {');
lines.push(' constructor(private client: ApiClient) {}');
lines.push('');
for (const op of parsed.operations) {
if (!op.operationId)
continue;
const methodName = op.operationId;
const hasParams = parsed.spec.paths?.[op.path]?.[op.method.toLowerCase()]?.parameters;
const hasBody = ['POST', 'PUT', 'PATCH'].includes(op.method);
lines.push(` async ${methodName}(`);
const params = [];
if (hasParams || hasBody) {
params.push('request: any');
}
lines.push(`${params.join(', ')}): Promise<any> {`);
lines.push(` return this.client.request(`);
lines.push(` '${op.method}',`);
lines.push(` '${op.path}',`);
if (hasParams || hasBody) {
lines.push(' {');
if (hasParams) {
lines.push(' params: request.query,');
}
if (hasBody) {
lines.push(' data: request.body,');
}
lines.push(' }');
}
lines.push(' );');
lines.push(' }');
lines.push('');
}
lines.push('}');
return lines.join('\n');
}
function generateHooks(parsed, _config) {
const lines = [];
lines.push(`import { useState, useEffect, useCallback } from 'react';`);
lines.push(`import { Api } from './api';`);
lines.push(`import { ApiClient } from './client';`);
lines.push('');
lines.push('export interface UseApiOptions {');
lines.push(' enabled?: boolean;');
lines.push(' onSuccess?: (data: any) => void;');
lines.push(' onError?: (error: Error) => void;');
lines.push('}');
lines.push('');
lines.push('export interface UseApiResult<T> {');
lines.push(' data: T | null;');
lines.push(' loading: boolean;');
lines.push(' error: Error | null;');
lines.push(' refetch: () => Promise<void>;');
lines.push('}');
lines.push('');
lines.push('const defaultClient = new ApiClient({');
lines.push(` baseURL: '${_config.baseUrl || parsed.baseUrl}',`);
lines.push('});');
lines.push('');
lines.push('const api = new Api(defaultClient);');
lines.push('');
lines.push('export function useApi<T>(');
lines.push(' operation: keyof Api,');
lines.push(' params?: any,');
lines.push(' options?: UseApiOptions');
lines.push('): UseApiResult<T> {');
lines.push(' const [data, setData] = useState<T | null>(null);');
lines.push(' const [loading, setLoading] = useState(false);');
lines.push(' const [error, setError] = useState<Error | null>(null);');
lines.push('');
lines.push(' const fetchData = useCallback(async () => {');
lines.push(' if (options?.enabled === false) return;');
lines.push('');
lines.push(' setLoading(true);');
lines.push(' setError(null);');
lines.push('');
lines.push(' try {');
lines.push(' const method = api[operation] as any;');
lines.push(' const result = await method.call(api, params);');
lines.push(' setData(result);');
lines.push(' options?.onSuccess?.(result);');
lines.push(' } catch (err) {');
lines.push(' const error = err instanceof Error ? err : new Error(String(err));');
lines.push(' setError(error);');
lines.push(' options?.onError?.(error);');
lines.push(' } finally {');
lines.push(' setLoading(false);');
lines.push(' }');
lines.push(' }, [operation, params, options]);');
lines.push('');
lines.push(' useEffect(() => {');
lines.push(' fetchData();');
lines.push(' }, [fetchData]);');
lines.push('');
lines.push(' return { data, loading, error, refetch: fetchData };');
lines.push('}');
return lines.join('\n');
}
function generateErrorHandling() {
const lines = [];
lines.push('export class ApiError extends Error {');
lines.push(' constructor(');
lines.push(' public statusCode: number,');
lines.push(' message: string,');
lines.push(' public response?: any');
lines.push(' ) {');
lines.push(' super(message);');
lines.push(' this.name = \'ApiError\';');
lines.push(' }');
lines.push('}');
lines.push('');
lines.push('export class NetworkError extends Error {');
lines.push(' constructor(message: string) {');
lines.push(' super(message);');
lines.push(' this.name = \'NetworkError\';');
lines.push(' }');
lines.push('}');
lines.push('');
lines.push('export class TimeoutError extends Error {');
lines.push(' constructor(message: string = \'Request timeout\') {');
lines.push(' super(message);');
lines.push(' this.name = \'TimeoutError\';');
lines.push(' }');
lines.push('}');
lines.push('');
lines.push('export function isApiError(error: unknown): error is ApiError {');
lines.push(' return error instanceof ApiError;');
lines.push('}');
lines.push('');
lines.push('export function handleApiError(error: unknown): string {');
lines.push(' if (isApiError(error)) {');
lines.push(' switch (error.statusCode) {');
lines.push(' case 400:');
lines.push(' return \'Bad request. Please check your input.\';');
lines.push(' case 401:');
lines.push(' return \'Unauthorized. Please login again.\';');
lines.push(' case 403:');
lines.push(' return \'Forbidden. You don\\\'t have permission to access this resource.\';');
lines.push(' case 404:');
lines.push(' return \'Resource not found.\';');
lines.push(' case 500:');
lines.push(' return \'Server error. Please try again later.\';');
lines.push(' default:');
lines.push(' return error.message || \'An error occurred.\';');
lines.push(' }');
lines.push(' }');
lines.push('');
lines.push(' if (error instanceof NetworkError) {');
lines.push(' return \'Network error. Please check your connection.\';');
lines.push(' }');
lines.push('');
lines.push(' if (error instanceof TimeoutError) {');
lines.push(' return \'Request timed out. Please try again.\';');
lines.push(' }');
lines.push('');
lines.push(' return \'An unexpected error occurred.\';');
lines.push('}');
return lines.join('\n');
}
function generateIndexFile(config) {
const lines = [];
lines.push(`export * from './client';`);
lines.push(`export * from './api';`);
if (config.features?.hooks) {
lines.push(`export * from './hooks';`);
}
if (config.features?.errorHandling) {
lines.push(`export * from './errors';`);
}
lines.push(`export * from './types';`);
return lines.join('\n');
}
//# sourceMappingURL=react-native.js.map