UNPKG

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
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