atlas-pipeline-mcp 1.0.25 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,907 @@
1
+ /**
2
+ * API Integration Helper
3
+ *
4
+ * Helps frontend developers integrate with backend APIs:
5
+ * - Generates TypeScript types from API responses
6
+ * - Creates React Query / SWR / TanStack Query hooks
7
+ * - Generates mock data for testing
8
+ * - Creates error handling wrappers
9
+ * - Builds API client with interceptors
10
+ * - Generates OpenAPI types
11
+ * - Creates Zod validation schemas
12
+ */
13
+ import { z } from 'zod';
14
+ import { logger } from '../utils.js';
15
+ // ============================================================================
16
+ // Validation Schema
17
+ // ============================================================================
18
+ export const APIEndpointSchema = z.object({
19
+ method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
20
+ path: z.string().min(1),
21
+ description: z.string().optional(),
22
+ requestBody: z.record(z.any()).optional(),
23
+ responseBody: z.record(z.any()),
24
+ headers: z.record(z.string()).optional(),
25
+ queryParams: z.record(z.string()).optional()
26
+ });
27
+ export const APIIntegrationRequestSchema = z.object({
28
+ endpoints: z.array(APIEndpointSchema).min(1),
29
+ baseUrl: z.string().url(),
30
+ library: z.enum(['react-query', 'swr', 'axios', 'fetch', 'tanstack-query']),
31
+ generateTypes: z.boolean().optional().default(true),
32
+ generateMocks: z.boolean().optional().default(true),
33
+ generateZodSchemas: z.boolean().optional().default(true),
34
+ authType: z.enum(['bearer', 'api-key', 'basic', 'oauth2', 'none']).optional().default('bearer'),
35
+ framework: z.enum(['react', 'vue', 'svelte', 'next']).optional().default('react')
36
+ });
37
+ // ============================================================================
38
+ // Type Inference Helpers
39
+ // ============================================================================
40
+ function inferTypeFromValue(value, key = '') {
41
+ if (value === null)
42
+ return 'null';
43
+ if (value === undefined)
44
+ return 'undefined';
45
+ const type = typeof value;
46
+ if (type === 'string') {
47
+ // Check for common patterns
48
+ if (/^\d{4}-\d{2}-\d{2}/.test(value))
49
+ return 'string'; // Date ISO string
50
+ if (/^[a-f0-9-]{36}$/i.test(value))
51
+ return 'string'; // UUID
52
+ if (value.includes('@'))
53
+ return 'string'; // Email
54
+ return 'string';
55
+ }
56
+ if (type === 'number') {
57
+ return Number.isInteger(value) ? 'number' : 'number';
58
+ }
59
+ if (type === 'boolean')
60
+ return 'boolean';
61
+ if (Array.isArray(value)) {
62
+ if (value.length === 0)
63
+ return 'unknown[]';
64
+ const itemType = inferTypeFromValue(value[0], key);
65
+ return `${itemType}[]`;
66
+ }
67
+ if (type === 'object') {
68
+ return 'object'; // Will be expanded later
69
+ }
70
+ return 'unknown';
71
+ }
72
+ function generateTypeFromObject(obj, name, depth = 0) {
73
+ const indent = ' '.repeat(depth);
74
+ const lines = [];
75
+ lines.push(`${indent}export interface ${name} {`);
76
+ for (const [key, value] of Object.entries(obj)) {
77
+ const isOptional = key.endsWith('?') || value === null;
78
+ const cleanKey = key.replace('?', '');
79
+ const optionalMark = isOptional ? '?' : '';
80
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
81
+ // Nested object - generate inline or reference
82
+ const nestedType = `${name}${toPascalCase(cleanKey)}`;
83
+ lines.push(`${indent} ${cleanKey}${optionalMark}: ${nestedType};`);
84
+ }
85
+ else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') {
86
+ // Array of objects
87
+ const itemType = `${name}${toPascalCase(cleanKey)}Item`;
88
+ lines.push(`${indent} ${cleanKey}${optionalMark}: ${itemType}[];`);
89
+ }
90
+ else {
91
+ const inferredType = inferTypeFromValue(value, cleanKey);
92
+ lines.push(`${indent} ${cleanKey}${optionalMark}: ${inferredType};`);
93
+ }
94
+ }
95
+ lines.push(`${indent}}`);
96
+ // Generate nested interfaces
97
+ for (const [key, value] of Object.entries(obj)) {
98
+ const cleanKey = key.replace('?', '');
99
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
100
+ lines.push('');
101
+ lines.push(generateTypeFromObject(value, `${name}${toPascalCase(cleanKey)}`, depth));
102
+ }
103
+ else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') {
104
+ lines.push('');
105
+ lines.push(generateTypeFromObject(value[0], `${name}${toPascalCase(cleanKey)}Item`, depth));
106
+ }
107
+ }
108
+ return lines.join('\n');
109
+ }
110
+ // ============================================================================
111
+ // Main Functions
112
+ // ============================================================================
113
+ export async function generateAPIIntegration(request) {
114
+ const validated = APIIntegrationRequestSchema.parse(request);
115
+ logger.info(`Generating API integration for ${validated.endpoints.length} endpoints`);
116
+ // Generate TypeScript types
117
+ const types = generateTypes(validated.endpoints);
118
+ // Generate API client
119
+ const apiClient = generateAPIClient(validated);
120
+ // Generate hooks based on library
121
+ const hooks = generateHooks(validated);
122
+ // Generate error handling
123
+ const errorHandling = generateErrorHandling(validated);
124
+ // Generate mocks if requested
125
+ const mocks = validated.generateMocks ? generateMocks(validated.endpoints) : undefined;
126
+ // Generate Zod schemas if requested
127
+ const zodSchemas = validated.generateZodSchemas ? generateZodSchemas(validated.endpoints) : undefined;
128
+ // Generate usage examples
129
+ const usageExamples = generateUsageExamples(validated);
130
+ // Generate testing guide
131
+ const testingGuide = generateTestingGuide(validated);
132
+ return {
133
+ types,
134
+ hooks,
135
+ apiClient,
136
+ mocks,
137
+ zodSchemas,
138
+ errorHandling,
139
+ usageExamples,
140
+ testingGuide
141
+ };
142
+ }
143
+ // ============================================================================
144
+ // Generator Functions
145
+ // ============================================================================
146
+ function generateTypes(endpoints) {
147
+ const types = [];
148
+ types.push(`// ============================================================================`);
149
+ types.push(`// API Types - Auto-generated`);
150
+ types.push(`// ============================================================================`);
151
+ types.push('');
152
+ for (const endpoint of endpoints) {
153
+ const pathName = pathToName(endpoint.path);
154
+ // Request type (for POST, PUT, PATCH)
155
+ if (endpoint.requestBody && ['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
156
+ types.push(generateTypeFromObject(endpoint.requestBody, `${pathName}Request`));
157
+ types.push('');
158
+ }
159
+ // Response type
160
+ types.push(generateTypeFromObject(endpoint.responseBody, `${pathName}Response`));
161
+ types.push('');
162
+ // Query params type
163
+ if (endpoint.queryParams) {
164
+ const queryInterface = Object.entries(endpoint.queryParams)
165
+ .map(([key, desc]) => ` ${key}?: string; // ${desc}`)
166
+ .join('\n');
167
+ types.push(`export interface ${pathName}QueryParams {\n${queryInterface}\n}`);
168
+ types.push('');
169
+ }
170
+ }
171
+ // Add common types
172
+ types.push(`// Common Types`);
173
+ types.push(`export interface APIError {
174
+ message: string;
175
+ code: string;
176
+ status: number;
177
+ details?: Record<string, string[]>;
178
+ }
179
+
180
+ export interface PaginatedResponse<T> {
181
+ data: T[];
182
+ pagination: {
183
+ page: number;
184
+ limit: number;
185
+ total: number;
186
+ totalPages: number;
187
+ };
188
+ }
189
+
190
+ export interface APIResponse<T> {
191
+ data: T;
192
+ message?: string;
193
+ success: boolean;
194
+ }`);
195
+ return types.join('\n');
196
+ }
197
+ function generateAPIClient(request) {
198
+ const { baseUrl, authType } = request;
199
+ return `// ============================================================================
200
+ // API Client - Auto-generated
201
+ // ============================================================================
202
+
203
+ const API_BASE_URL = '${baseUrl}';
204
+
205
+ // Token storage (use secure storage in production)
206
+ let authToken: string | null = null;
207
+
208
+ export const setAuthToken = (token: string) => {
209
+ authToken = token;
210
+ };
211
+
212
+ export const clearAuthToken = () => {
213
+ authToken = null;
214
+ };
215
+
216
+ // Request interceptor
217
+ const getHeaders = (customHeaders?: Record<string, string>): HeadersInit => {
218
+ const headers: Record<string, string> = {
219
+ 'Content-Type': 'application/json',
220
+ ...customHeaders,
221
+ };
222
+
223
+ ${authType === 'bearer' ? `if (authToken) {
224
+ headers['Authorization'] = \`Bearer \${authToken}\`;
225
+ }` : ''}
226
+ ${authType === 'api-key' ? `if (authToken) {
227
+ headers['X-API-Key'] = authToken;
228
+ }` : ''}
229
+
230
+ return headers;
231
+ };
232
+
233
+ // Response interceptor
234
+ const handleResponse = async <T>(response: Response): Promise<T> => {
235
+ if (!response.ok) {
236
+ const error = await response.json().catch(() => ({
237
+ message: response.statusText,
238
+ code: 'UNKNOWN_ERROR',
239
+ status: response.status,
240
+ }));
241
+
242
+ // Handle specific error codes
243
+ if (response.status === 401) {
244
+ // Token expired - trigger refresh or logout
245
+ clearAuthToken();
246
+ window.dispatchEvent(new CustomEvent('auth:expired'));
247
+ }
248
+
249
+ throw error;
250
+ }
251
+
252
+ return response.json();
253
+ };
254
+
255
+ // Generic request function
256
+ export async function apiRequest<T>(
257
+ endpoint: string,
258
+ options: RequestInit = {}
259
+ ): Promise<T> {
260
+ const url = \`\${API_BASE_URL}\${endpoint}\`;
261
+
262
+ const response = await fetch(url, {
263
+ ...options,
264
+ headers: getHeaders(options.headers as Record<string, string>),
265
+ });
266
+
267
+ return handleResponse<T>(response);
268
+ }
269
+
270
+ // Convenience methods
271
+ export const api = {
272
+ get: <T>(endpoint: string, params?: Record<string, string>) => {
273
+ const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
274
+ return apiRequest<T>(\`\${endpoint}\${queryString}\`, { method: 'GET' });
275
+ },
276
+
277
+ post: <T>(endpoint: string, data?: unknown) => {
278
+ return apiRequest<T>(endpoint, {
279
+ method: 'POST',
280
+ body: data ? JSON.stringify(data) : undefined,
281
+ });
282
+ },
283
+
284
+ put: <T>(endpoint: string, data?: unknown) => {
285
+ return apiRequest<T>(endpoint, {
286
+ method: 'PUT',
287
+ body: data ? JSON.stringify(data) : undefined,
288
+ });
289
+ },
290
+
291
+ patch: <T>(endpoint: string, data?: unknown) => {
292
+ return apiRequest<T>(endpoint, {
293
+ method: 'PATCH',
294
+ body: data ? JSON.stringify(data) : undefined,
295
+ });
296
+ },
297
+
298
+ delete: <T>(endpoint: string) => {
299
+ return apiRequest<T>(endpoint, { method: 'DELETE' });
300
+ },
301
+ };
302
+
303
+ export default api;`;
304
+ }
305
+ function generateHooks(request) {
306
+ const { library, endpoints } = request;
307
+ switch (library) {
308
+ case 'react-query':
309
+ case 'tanstack-query':
310
+ return generateTanStackQueryHooks(endpoints);
311
+ case 'swr':
312
+ return generateSWRHooks(endpoints);
313
+ default:
314
+ return generateFetchHooks(endpoints);
315
+ }
316
+ }
317
+ function generateTanStackQueryHooks(endpoints) {
318
+ const hooks = [];
319
+ hooks.push(`// ============================================================================`);
320
+ hooks.push(`// React Query / TanStack Query Hooks - Auto-generated`);
321
+ hooks.push(`// ============================================================================`);
322
+ hooks.push('');
323
+ hooks.push(`import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';`);
324
+ hooks.push(`import { api } from './api-client';`);
325
+ hooks.push(`import type { ${endpoints.map(e => `${pathToName(e.path)}Response`).join(', ')} } from './types';`);
326
+ hooks.push('');
327
+ hooks.push(`// Query Keys`);
328
+ hooks.push(`export const queryKeys = {`);
329
+ for (const endpoint of endpoints) {
330
+ const name = pathToName(endpoint.path);
331
+ const keyName = toCamelCase(name);
332
+ hooks.push(` ${keyName}: ['${keyName}'] as const,`);
333
+ }
334
+ hooks.push(`};`);
335
+ hooks.push('');
336
+ for (const endpoint of endpoints) {
337
+ const name = pathToName(endpoint.path);
338
+ const hookName = toCamelCase(name);
339
+ if (endpoint.method === 'GET') {
340
+ hooks.push(`// ${endpoint.description || `Fetch ${name}`}
341
+ export function use${name}(${hasPathParams(endpoint.path) ? 'id: string, ' : ''}options?: { enabled?: boolean }) {
342
+ return useQuery({
343
+ queryKey: [...queryKeys.${hookName}${hasPathParams(endpoint.path) ? ', id' : ''}],
344
+ queryFn: () => api.get<${name}Response>('${endpoint.path}'${hasPathParams(endpoint.path) ? '.replace(":id", id)' : ''}),
345
+ ...options,
346
+ });
347
+ }
348
+ `);
349
+ }
350
+ else if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
351
+ const inputType = endpoint.requestBody ? `${name}Request` : 'unknown';
352
+ hooks.push(`// ${endpoint.description || `${endpoint.method} ${name}`}
353
+ export function use${name}Mutation() {
354
+ const queryClient = useQueryClient();
355
+
356
+ return useMutation({
357
+ mutationFn: (data: ${inputType}) =>
358
+ api.${endpoint.method.toLowerCase()}<${name}Response>('${endpoint.path}', data),
359
+ onSuccess: () => {
360
+ // Invalidate related queries
361
+ queryClient.invalidateQueries({ queryKey: queryKeys.${hookName} });
362
+ },
363
+ });
364
+ }
365
+ `);
366
+ }
367
+ else if (endpoint.method === 'DELETE') {
368
+ hooks.push(`// ${endpoint.description || `Delete ${name}`}
369
+ export function useDelete${name}() {
370
+ const queryClient = useQueryClient();
371
+
372
+ return useMutation({
373
+ mutationFn: (id: string) =>
374
+ api.delete<${name}Response>(\`${endpoint.path.replace(':id', '${id}')}\`),
375
+ onSuccess: () => {
376
+ queryClient.invalidateQueries({ queryKey: queryKeys.${hookName} });
377
+ },
378
+ });
379
+ }
380
+ `);
381
+ }
382
+ }
383
+ return hooks.join('\n');
384
+ }
385
+ function generateSWRHooks(endpoints) {
386
+ const hooks = [];
387
+ hooks.push(`// ============================================================================`);
388
+ hooks.push(`// SWR Hooks - Auto-generated`);
389
+ hooks.push(`// ============================================================================`);
390
+ hooks.push('');
391
+ hooks.push(`import useSWR from 'swr';`);
392
+ hooks.push(`import useSWRMutation from 'swr/mutation';`);
393
+ hooks.push(`import { api } from './api-client';`);
394
+ hooks.push('');
395
+ hooks.push(`// SWR Fetcher`);
396
+ hooks.push(`const fetcher = <T>(url: string): Promise<T> => api.get<T>(url);`);
397
+ hooks.push('');
398
+ for (const endpoint of endpoints) {
399
+ const name = pathToName(endpoint.path);
400
+ const hookName = toCamelCase(name);
401
+ if (endpoint.method === 'GET') {
402
+ hooks.push(`// ${endpoint.description || `Fetch ${name}`}
403
+ export function use${name}(${hasPathParams(endpoint.path) ? 'id: string' : ''}) {
404
+ return useSWR<${name}Response>(
405
+ ${hasPathParams(endpoint.path) ? `id ? \`${endpoint.path.replace(':id', '${id}')}\` : null` : `'${endpoint.path}'`},
406
+ fetcher,
407
+ {
408
+ revalidateOnFocus: false,
409
+ dedupingInterval: 5000,
410
+ }
411
+ );
412
+ }
413
+ `);
414
+ }
415
+ else if (['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
416
+ hooks.push(`// ${endpoint.description || `${endpoint.method} ${name}`}
417
+ export function use${name}Mutation() {
418
+ return useSWRMutation(
419
+ '${endpoint.path}',
420
+ (url: string, { arg }: { arg: ${name}Request }) =>
421
+ api.${endpoint.method.toLowerCase()}<${name}Response>(url, arg)
422
+ );
423
+ }
424
+ `);
425
+ }
426
+ }
427
+ return hooks.join('\n');
428
+ }
429
+ function generateFetchHooks(endpoints) {
430
+ const hooks = [];
431
+ hooks.push(`// ============================================================================`);
432
+ hooks.push(`// Custom Fetch Hooks - Auto-generated`);
433
+ hooks.push(`// ============================================================================`);
434
+ hooks.push('');
435
+ hooks.push(`import { useState, useEffect, useCallback } from 'react';`);
436
+ hooks.push(`import { api } from './api-client';`);
437
+ hooks.push('');
438
+ hooks.push(`// Generic fetch hook
439
+ interface UseFetchResult<T> {
440
+ data: T | null;
441
+ loading: boolean;
442
+ error: Error | null;
443
+ refetch: () => void;
444
+ }
445
+
446
+ function useFetch<T>(fetchFn: () => Promise<T>): UseFetchResult<T> {
447
+ const [data, setData] = useState<T | null>(null);
448
+ const [loading, setLoading] = useState(true);
449
+ const [error, setError] = useState<Error | null>(null);
450
+
451
+ const fetch = useCallback(async () => {
452
+ setLoading(true);
453
+ setError(null);
454
+ try {
455
+ const result = await fetchFn();
456
+ setData(result);
457
+ } catch (err) {
458
+ setError(err instanceof Error ? err : new Error('Unknown error'));
459
+ } finally {
460
+ setLoading(false);
461
+ }
462
+ }, [fetchFn]);
463
+
464
+ useEffect(() => {
465
+ fetch();
466
+ }, [fetch]);
467
+
468
+ return { data, loading, error, refetch: fetch };
469
+ }
470
+ `);
471
+ for (const endpoint of endpoints) {
472
+ const name = pathToName(endpoint.path);
473
+ if (endpoint.method === 'GET') {
474
+ hooks.push(`// ${endpoint.description || `Fetch ${name}`}
475
+ export function use${name}(${hasPathParams(endpoint.path) ? 'id: string' : ''}) {
476
+ return useFetch(() => api.get<${name}Response>('${endpoint.path}'${hasPathParams(endpoint.path) ? '.replace(":id", id)' : ''}));
477
+ }
478
+ `);
479
+ }
480
+ }
481
+ return hooks.join('\n');
482
+ }
483
+ function generateErrorHandling(request) {
484
+ return `// ============================================================================
485
+ // Error Handling - Auto-generated
486
+ // ============================================================================
487
+
488
+ import { toast } from 'sonner'; // or your preferred toast library
489
+
490
+ export interface APIError {
491
+ message: string;
492
+ code: string;
493
+ status: number;
494
+ details?: Record<string, string[]>;
495
+ }
496
+
497
+ // Error codes mapping
498
+ const ERROR_MESSAGES: Record<string, string> = {
499
+ NETWORK_ERROR: 'Unable to connect. Please check your internet connection.',
500
+ UNAUTHORIZED: 'Your session has expired. Please log in again.',
501
+ FORBIDDEN: 'You don\\'t have permission to perform this action.',
502
+ NOT_FOUND: 'The requested resource was not found.',
503
+ VALIDATION_ERROR: 'Please check your input and try again.',
504
+ RATE_LIMITED: 'Too many requests. Please wait a moment.',
505
+ SERVER_ERROR: 'Something went wrong. Please try again later.',
506
+ UNKNOWN_ERROR: 'An unexpected error occurred.',
507
+ };
508
+
509
+ // Error handler
510
+ export function handleAPIError(error: APIError): void {
511
+ const message = ERROR_MESSAGES[error.code] || error.message || ERROR_MESSAGES.UNKNOWN_ERROR;
512
+
513
+ // Show toast notification
514
+ toast.error(message);
515
+
516
+ // Log for debugging (remove in production)
517
+ console.error('[API Error]', {
518
+ code: error.code,
519
+ message: error.message,
520
+ status: error.status,
521
+ details: error.details,
522
+ });
523
+
524
+ // Track error analytics
525
+ // analytics.track('api_error', error);
526
+ }
527
+
528
+ // React Query error handler
529
+ export const queryErrorHandler = (error: unknown) => {
530
+ if (isAPIError(error)) {
531
+ handleAPIError(error);
532
+ } else if (error instanceof Error) {
533
+ handleAPIError({
534
+ message: error.message,
535
+ code: 'UNKNOWN_ERROR',
536
+ status: 0,
537
+ });
538
+ }
539
+ };
540
+
541
+ // Type guard
542
+ function isAPIError(error: unknown): error is APIError {
543
+ return (
544
+ typeof error === 'object' &&
545
+ error !== null &&
546
+ 'message' in error &&
547
+ 'code' in error
548
+ );
549
+ }
550
+
551
+ // Retry logic
552
+ export const retryCondition = (failureCount: number, error: unknown): boolean => {
553
+ // Don't retry on 4xx errors
554
+ if (isAPIError(error) && error.status >= 400 && error.status < 500) {
555
+ return false;
556
+ }
557
+ // Retry up to 3 times for server errors
558
+ return failureCount < 3;
559
+ };
560
+
561
+ // React Query default options with error handling
562
+ export const queryClientConfig = {
563
+ defaultOptions: {
564
+ queries: {
565
+ retry: retryCondition,
566
+ staleTime: 5 * 60 * 1000, // 5 minutes
567
+ gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
568
+ },
569
+ mutations: {
570
+ retry: false,
571
+ onError: queryErrorHandler,
572
+ },
573
+ },
574
+ };`;
575
+ }
576
+ function generateMocks(endpoints) {
577
+ const mocks = [];
578
+ mocks.push(`// ============================================================================`);
579
+ mocks.push(`// Mock Data - Auto-generated`);
580
+ mocks.push(`// ============================================================================`);
581
+ mocks.push('');
582
+ mocks.push(`import { http, HttpResponse } from 'msw';`);
583
+ mocks.push('');
584
+ mocks.push(`// Mock Data Generators`);
585
+ mocks.push(`const generateId = () => Math.random().toString(36).substr(2, 9);`);
586
+ mocks.push(`const generateDate = () => new Date().toISOString();`);
587
+ mocks.push('');
588
+ // Generate mock data for each endpoint
589
+ for (const endpoint of endpoints) {
590
+ const name = pathToName(endpoint.path);
591
+ const mockData = generateMockData(endpoint.responseBody, name);
592
+ mocks.push(`export const mock${name} = ${mockData};`);
593
+ mocks.push('');
594
+ }
595
+ // Generate MSW handlers
596
+ mocks.push(`// MSW Handlers`);
597
+ mocks.push(`export const handlers = [`);
598
+ for (const endpoint of endpoints) {
599
+ const name = pathToName(endpoint.path);
600
+ const method = endpoint.method.toLowerCase();
601
+ const path = endpoint.path.replace(/:(\w+)/g, ':$1'); // MSW path format
602
+ mocks.push(` http.${method}('${path}', () => {
603
+ return HttpResponse.json(mock${name});
604
+ }),`);
605
+ }
606
+ mocks.push(`];`);
607
+ mocks.push('');
608
+ // Add MSW setup
609
+ mocks.push(`// MSW Setup (add to your test setup file)
610
+ /*
611
+ import { setupServer } from 'msw/node';
612
+ import { handlers } from './mocks';
613
+
614
+ export const server = setupServer(...handlers);
615
+
616
+ beforeAll(() => server.listen());
617
+ afterEach(() => server.resetHandlers());
618
+ afterAll(() => server.close());
619
+ */`);
620
+ return mocks.join('\n');
621
+ }
622
+ function generateMockData(obj, prefix) {
623
+ const mockObj = {};
624
+ for (const [key, value] of Object.entries(obj)) {
625
+ if (typeof value === 'string') {
626
+ if (key.toLowerCase().includes('id')) {
627
+ mockObj[key] = 'mock-id-123';
628
+ }
629
+ else if (key.toLowerCase().includes('email')) {
630
+ mockObj[key] = 'user@example.com';
631
+ }
632
+ else if (key.toLowerCase().includes('name')) {
633
+ mockObj[key] = 'John Doe';
634
+ }
635
+ else if (key.toLowerCase().includes('date') || key.toLowerCase().includes('at')) {
636
+ mockObj[key] = new Date().toISOString();
637
+ }
638
+ else {
639
+ mockObj[key] = `Mock ${key}`;
640
+ }
641
+ }
642
+ else if (typeof value === 'number') {
643
+ mockObj[key] = 42;
644
+ }
645
+ else if (typeof value === 'boolean') {
646
+ mockObj[key] = true;
647
+ }
648
+ else if (Array.isArray(value)) {
649
+ mockObj[key] = value.length > 0 ? [generateMockData(value[0], key)] : [];
650
+ }
651
+ else if (typeof value === 'object' && value !== null) {
652
+ mockObj[key] = JSON.parse(generateMockData(value, key));
653
+ }
654
+ }
655
+ return JSON.stringify(mockObj, null, 2);
656
+ }
657
+ function generateZodSchemas(endpoints) {
658
+ const schemas = [];
659
+ schemas.push(`// ============================================================================`);
660
+ schemas.push(`// Zod Validation Schemas - Auto-generated`);
661
+ schemas.push(`// ============================================================================`);
662
+ schemas.push('');
663
+ schemas.push(`import { z } from 'zod';`);
664
+ schemas.push('');
665
+ for (const endpoint of endpoints) {
666
+ const name = pathToName(endpoint.path);
667
+ // Response schema
668
+ schemas.push(`export const ${name}ResponseSchema = ${generateZodSchema(endpoint.responseBody)};`);
669
+ schemas.push(`export type ${name}Response = z.infer<typeof ${name}ResponseSchema>;`);
670
+ schemas.push('');
671
+ // Request schema (for mutations)
672
+ if (endpoint.requestBody && ['POST', 'PUT', 'PATCH'].includes(endpoint.method)) {
673
+ schemas.push(`export const ${name}RequestSchema = ${generateZodSchema(endpoint.requestBody)};`);
674
+ schemas.push(`export type ${name}Request = z.infer<typeof ${name}RequestSchema>;`);
675
+ schemas.push('');
676
+ }
677
+ }
678
+ // Add validation helper
679
+ schemas.push(`// Validation helper
680
+ export function validateResponse<T>(schema: z.ZodSchema<T>, data: unknown): T {
681
+ const result = schema.safeParse(data);
682
+ if (!result.success) {
683
+ console.error('Validation error:', result.error.format());
684
+ throw new Error('Invalid API response');
685
+ }
686
+ return result.data;
687
+ }`);
688
+ return schemas.join('\n');
689
+ }
690
+ function generateZodSchema(obj) {
691
+ const fields = [];
692
+ for (const [key, value] of Object.entries(obj)) {
693
+ const isOptional = key.endsWith('?');
694
+ const cleanKey = key.replace('?', '');
695
+ let zodType;
696
+ if (typeof value === 'string') {
697
+ zodType = 'z.string()';
698
+ }
699
+ else if (typeof value === 'number') {
700
+ zodType = 'z.number()';
701
+ }
702
+ else if (typeof value === 'boolean') {
703
+ zodType = 'z.boolean()';
704
+ }
705
+ else if (Array.isArray(value)) {
706
+ if (value.length > 0 && typeof value[0] === 'object') {
707
+ zodType = `z.array(${generateZodSchema(value[0])})`;
708
+ }
709
+ else {
710
+ zodType = 'z.array(z.unknown())';
711
+ }
712
+ }
713
+ else if (typeof value === 'object' && value !== null) {
714
+ zodType = generateZodSchema(value);
715
+ }
716
+ else {
717
+ zodType = 'z.unknown()';
718
+ }
719
+ if (isOptional) {
720
+ zodType += '.optional()';
721
+ }
722
+ fields.push(` ${cleanKey}: ${zodType}`);
723
+ }
724
+ return `z.object({\n${fields.join(',\n')}\n})`;
725
+ }
726
+ function generateUsageExamples(request) {
727
+ const { library, endpoints, framework } = request;
728
+ const example = endpoints[0];
729
+ if (!example) {
730
+ return '// No endpoints provided for usage examples';
731
+ }
732
+ const name = pathToName(example.path);
733
+ if (library === 'react-query' || library === 'tanstack-query') {
734
+ return `// ============================================================================
735
+ // Usage Examples - React Query / TanStack Query
736
+ // ============================================================================
737
+
738
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
739
+ import { use${name} } from './hooks';
740
+
741
+ // 1. Setup Query Client
742
+ const queryClient = new QueryClient(queryClientConfig);
743
+
744
+ function App() {
745
+ return (
746
+ <QueryClientProvider client={queryClient}>
747
+ <YourApp />
748
+ </QueryClientProvider>
749
+ );
750
+ }
751
+
752
+ // 2. Use in Component
753
+ function ${name}List() {
754
+ const { data, isLoading, error, refetch } = use${name}();
755
+
756
+ if (isLoading) return <div>Loading...</div>;
757
+ if (error) return <div>Error: {error.message}</div>;
758
+
759
+ return (
760
+ <div>
761
+ {/* Render your data */}
762
+ <pre>{JSON.stringify(data, null, 2)}</pre>
763
+ <button onClick={() => refetch()}>Refresh</button>
764
+ </div>
765
+ );
766
+ }
767
+
768
+ // 3. Mutations
769
+ function Create${name}Form() {
770
+ const mutation = use${name}Mutation();
771
+
772
+ const handleSubmit = (data) => {
773
+ mutation.mutate(data, {
774
+ onSuccess: () => {
775
+ toast.success('Created successfully!');
776
+ },
777
+ onError: (error) => {
778
+ handleAPIError(error);
779
+ },
780
+ });
781
+ };
782
+
783
+ return (
784
+ <form onSubmit={handleSubmit}>
785
+ {/* Your form fields */}
786
+ <button type="submit" disabled={mutation.isPending}>
787
+ {mutation.isPending ? 'Saving...' : 'Save'}
788
+ </button>
789
+ </form>
790
+ );
791
+ }`;
792
+ }
793
+ return `// See the generated hooks file for usage examples`;
794
+ }
795
+ function generateTestingGuide(request) {
796
+ return `// ============================================================================
797
+ // Testing Guide - Auto-generated
798
+ // ============================================================================
799
+
800
+ /*
801
+ ## 1. Setup MSW (Mock Service Worker)
802
+
803
+ npm install msw --save-dev
804
+
805
+ Create src/mocks/handlers.ts (use the generated mocks file)
806
+ Create src/mocks/browser.ts for browser
807
+ Create src/mocks/server.ts for tests
808
+
809
+ ## 2. Testing Hooks
810
+
811
+ \`\`\`typescript
812
+ import { renderHook, waitFor } from '@testing-library/react';
813
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
814
+ import { useUsers } from './hooks';
815
+
816
+ const createWrapper = () => {
817
+ const queryClient = new QueryClient({
818
+ defaultOptions: {
819
+ queries: { retry: false },
820
+ },
821
+ });
822
+
823
+ return ({ children }) => (
824
+ <QueryClientProvider client={queryClient}>
825
+ {children}
826
+ </QueryClientProvider>
827
+ );
828
+ };
829
+
830
+ describe('useUsers', () => {
831
+ it('fetches users successfully', async () => {
832
+ const { result } = renderHook(() => useUsers(), {
833
+ wrapper: createWrapper(),
834
+ });
835
+
836
+ await waitFor(() => {
837
+ expect(result.current.isSuccess).toBe(true);
838
+ });
839
+
840
+ expect(result.current.data).toBeDefined();
841
+ });
842
+
843
+ it('handles errors', async () => {
844
+ // Override handler for this test
845
+ server.use(
846
+ http.get('/users', () => {
847
+ return HttpResponse.json(
848
+ { message: 'Not found' },
849
+ { status: 404 }
850
+ );
851
+ })
852
+ );
853
+
854
+ const { result } = renderHook(() => useUsers(), {
855
+ wrapper: createWrapper(),
856
+ });
857
+
858
+ await waitFor(() => {
859
+ expect(result.current.isError).toBe(true);
860
+ });
861
+ });
862
+ });
863
+ \`\`\`
864
+
865
+ ## 3. Integration Testing
866
+
867
+ Test the full flow including API client, hooks, and components together.
868
+
869
+ ## 4. E2E Testing with Playwright
870
+
871
+ \`\`\`typescript
872
+ import { test, expect } from '@playwright/test';
873
+
874
+ test('loads and displays data', async ({ page }) => {
875
+ await page.goto('/');
876
+ await expect(page.getByText('Loading...')).toBeVisible();
877
+ await expect(page.getByText('John Doe')).toBeVisible();
878
+ });
879
+ \`\`\`
880
+ */`;
881
+ }
882
+ // ============================================================================
883
+ // Utility Functions
884
+ // ============================================================================
885
+ function pathToName(path) {
886
+ return path
887
+ .replace(/^\//, '')
888
+ .replace(/\/:?\w+/g, '')
889
+ .split(/[-_/]/)
890
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
891
+ .join('');
892
+ }
893
+ function toPascalCase(str) {
894
+ return str
895
+ .split(/[-_\s]+/)
896
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
897
+ .join('');
898
+ }
899
+ function toCamelCase(str) {
900
+ const pascal = toPascalCase(str);
901
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
902
+ }
903
+ function hasPathParams(path) {
904
+ return path.includes(':');
905
+ }
906
+ export default { generateAPIIntegration };
907
+ //# sourceMappingURL=api-integration-helper.js.map