UNPKG

breathe-api

Version:

Model Context Protocol server for Breathe HR APIs with Swagger/OpenAPI support - also works with custom APIs

375 lines 15.8 kB
export async function generateNextJsClient(parsed, config) { const files = {}; if (parsed.types) { files['types.ts'] = parsed.types; } files['client.ts'] = generateBaseClient(parsed, config); files['api.ts'] = generateApiMethods(parsed, config); files['actions.ts'] = generateServerActions(parsed, config); if (config.features?.hooks) { files['hooks.ts'] = generateHooks(parsed, config); } if (config.features?.errorHandling) { files['errors.ts'] = generateErrorHandling(); } if (config.authType !== 'none') { files['middleware.ts'] = generateMiddleware(config); } files['index.ts'] = generateIndexFile(config); return files; } function generateBaseClient(parsed, config) { const lines = []; lines.push(`import { cookies } from 'next/headers';`); lines.push(`import { cache } from 'react';`); 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(' cache?: RequestCache;'); lines.push(' revalidate?: 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(' cache: \'no-store\','); 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(' ...this.config.headers,'); lines.push(' };'); lines.push(''); if (config.authType === 'bearer') { lines.push(' if (typeof window === \'undefined\') {'); lines.push(' // Server-side: get token from cookies'); lines.push(' const cookieStore = await cookies();'); lines.push(' const token = cookieStore.get(\'authToken\')?.value || this.config.token;'); lines.push(' if (token) {'); lines.push(' headers.Authorization = `Bearer ${token}`;'); lines.push(' }'); lines.push(' } else {'); lines.push(' // Client-side: get token from localStorage or config'); lines.push(' const token = this.config.token || localStorage.getItem(\'authToken\');'); lines.push(' if (token) {'); lines.push(' headers.Authorization = `Bearer ${token}`;'); lines.push(' }'); lines.push(' }'); } else if (config.authType === 'api-key') { lines.push(' const apiKey = this.config.apiKey || process.env.NEXT_PUBLIC_API_KEY;'); 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(' cache?: RequestCache;'); lines.push(' revalidate?: number;'); 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 fetchOptions: RequestInit = {'); 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(' cache: options?.cache || this.config.cache,'); lines.push(' };'); lines.push(''); lines.push(' if (options?.revalidate !== undefined) {'); lines.push(' fetchOptions.next = { revalidate: options.revalidate };'); lines.push(' } else if (this.config.revalidate !== undefined) {'); lines.push(' fetchOptions.next = { revalidate: this.config.revalidate };'); lines.push(' }'); lines.push(''); lines.push(' const response = await fetch(url.toString(), fetchOptions);'); 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('}'); lines.push(''); lines.push('// Singleton instance for server components'); lines.push('export const apiClient = new ApiClient({'); lines.push(` baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || '${config.baseUrl || parsed.baseUrl}',`); 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'); } params.push('options?: { cache?: RequestCache; revalidate?: number }'); lines.push(`${params.join(', ')}): Promise<any> {`); lines.push(` return this.client.request(`); lines.push(` '${op.method}',`); lines.push(` '${op.path}',`); lines.push(' {'); if (hasParams) { lines.push(' params: request?.query,'); } if (hasBody) { lines.push(' data: request?.body,'); } lines.push(' ...options,'); lines.push(' }'); lines.push(' );'); lines.push(' }'); lines.push(''); } lines.push('}'); return lines.join('\n'); } function generateServerActions(parsed, config) { const lines = []; lines.push(`'use server';`); lines.push(''); lines.push(`import { apiClient } from './client';`); lines.push(`import { Api } from './api';`); if (config.features?.validation) { lines.push(`import { z } from 'zod';`); } lines.push(''); lines.push('const api = new Api(apiClient);'); lines.push(''); for (const op of parsed.operations.slice(0, 5)) { if (!op.operationId) continue; const actionName = `${op.operationId}Action`; lines.push(`export async function ${actionName}(formData: FormData) {`); lines.push(' try {'); if (config.features?.validation) { lines.push(' // Add validation schema here'); lines.push(' // const schema = z.object({ ... });'); lines.push(' // const validated = schema.parse(Object.fromEntries(formData));'); } lines.push(` const result = await api.${op.operationId}();`); lines.push(' return { success: true, data: result };'); lines.push(' } catch (error) {'); lines.push(' console.error(error);'); lines.push(' return { success: false, error: \'Failed to perform action\' };'); lines.push(' }'); lines.push('}'); lines.push(''); } return lines.join('\n'); } function generateHooks(parsed, config) { const lines = []; lines.push(`'use client';`); lines.push(''); lines.push(`import useSWR, { SWRConfiguration, SWRResponse } from 'swr';`); lines.push(`import { Api } from './api';`); lines.push(`import { ApiClient } from './client';`); lines.push(''); lines.push('// Create client instance for client-side usage'); lines.push('const clientApiClient = new ApiClient({'); lines.push(` baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || '${config.baseUrl || parsed.baseUrl}',`); lines.push('});'); lines.push(''); lines.push('const api = new Api(clientApiClient);'); lines.push(''); lines.push('export interface UseApiOptions extends SWRConfiguration {'); lines.push(' enabled?: boolean;'); lines.push('}'); lines.push(''); lines.push('export function useApi<T>('); lines.push(' operation: keyof Api,'); lines.push(' params?: any,'); lines.push(' options?: UseApiOptions'); lines.push('): SWRResponse<T> {'); lines.push(' const key = options?.enabled !== false ? [operation, params] : null;'); lines.push(''); lines.push(' return useSWR<T>('); lines.push(' key,'); lines.push(' async ([op, p]) => {'); lines.push(' const method = api[op] as any;'); lines.push(' return method.call(api, p);'); lines.push(' },'); lines.push(' options'); lines.push(' );'); lines.push('}'); lines.push(''); lines.push('// Specific hooks for common operations'); for (const op of parsed.operations.slice(0, 5)) { if (!op.operationId || op.method !== 'GET') continue; const hookName = `use${op.operationId.charAt(0).toUpperCase()}${op.operationId.slice(1)}`; lines.push(`export function ${hookName}(`); lines.push(' params?: any,'); lines.push(' options?: UseApiOptions'); lines.push('): SWRResponse<any> {'); lines.push(` return useApi('${op.operationId}', params, options);`); lines.push('}'); 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 function isApiError(error: unknown): error is ApiError {'); lines.push(' return error instanceof ApiError;'); lines.push('}'); lines.push(''); lines.push('export function getErrorMessage(error: unknown): string {'); lines.push(' if (isApiError(error)) {'); lines.push(' switch (error.statusCode) {'); lines.push(' case 400:'); lines.push(' return \'Invalid request. Please check your input.\';'); lines.push(' case 401:'); lines.push(' return \'You need to sign in to access this resource.\';'); lines.push(' case 403:'); lines.push(' return \'You don\\\'t have permission to perform this action.\';'); lines.push(' case 404:'); lines.push(' return \'The requested resource was not found.\';'); lines.push(' case 429:'); lines.push(' return \'Too many requests. Please try again later.\';'); lines.push(' case 500:'); lines.push(' return \'Something went wrong on our end. Please try again.\';'); lines.push(' default:'); lines.push(' return error.message || \'An unexpected error occurred.\';'); lines.push(' }'); lines.push(' }'); lines.push(''); lines.push(' if (error instanceof Error) {'); lines.push(' return error.message;'); lines.push(' }'); lines.push(''); lines.push(' return \'An unexpected error occurred.\';'); lines.push('}'); return lines.join('\n'); } function generateMiddleware(config) { const lines = []; lines.push(`import { NextResponse } from 'next/server';`); lines.push(`import type { NextRequest } from 'next/server';`); lines.push(''); lines.push('export function middleware(request: NextRequest) {'); if (config.authType === 'bearer') { lines.push(' const token = request.cookies.get(\'authToken\')?.value;'); lines.push(''); lines.push(' // Protect API routes'); lines.push(' if (request.nextUrl.pathname.startsWith(\'/api\')) {'); lines.push(' if (!token) {'); lines.push(' return NextResponse.json('); lines.push(' { error: \'Authentication required\' },'); lines.push(' { status: 401 }'); lines.push(' );'); lines.push(' }'); lines.push(' }'); lines.push(''); lines.push(' // Protect app routes'); lines.push(' if (request.nextUrl.pathname.startsWith(\'/dashboard\')) {'); lines.push(' if (!token) {'); lines.push(' return NextResponse.redirect(new URL(\'/login\', request.url));'); lines.push(' }'); lines.push(' }'); } lines.push(''); lines.push(' return NextResponse.next();'); lines.push('}'); lines.push(''); lines.push('export const config = {'); lines.push(' matcher: [\'/api/:path*\', \'/dashboard/:path*\'],'); lines.push('};'); return lines.join('\n'); } function generateIndexFile(config) { const lines = []; lines.push(`export * from './client';`); lines.push(`export * from './api';`); lines.push(`export * from './actions';`); 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=nextjs.js.map