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