UNPKG

ruch

Version:

Revolutionary React TypeScript CLI with hexagonal architecture & AI-powered development assistance. Create maintainable, scalable applications with domain-driven design and integrated AI tooling.

629 lines (539 loc) 19.7 kB
import type { DomainAnalysis, ParsedEntity, ParsedMethod, ParsedAdapter, ParsedPorts } from '../utils/domain-file-analyzer'; /** * Generate dynamic MSW handlers based on domain analysis */ export const generateDynamicMswHandlers = (analysis: DomainAnalysis): string => { const { domainName, entities, adapters, ports, primaryEntity } = analysis; const capitalizedDomain = domainName.charAt(0).toUpperCase() + domainName.slice(1); // Get primary adapter or fallback to first adapter const primaryAdapter = adapters.find(a => a.name.toLowerCase().includes('api') || a.name.toLowerCase().includes(domainName.toLowerCase()) ) || adapters[0]; const baseUrl = primaryAdapter?.baseUrl || `/api/${domainName}`; const mockDataPath = `../../../domains/${domainName}/mocks/mockData`; // Generate imports const imports = generateImports(entities, mockDataPath, capitalizedDomain); // Generate handlers based on ports (interface) and adapter for baseUrl const primaryPort = ports.find(p => p.name.toLowerCase().includes('port') || p.name.toLowerCase().includes(domainName.toLowerCase()) ) || ports[0]; const handlers = primaryPort ? generateHandlersFromPort(primaryPort, baseUrl, capitalizedDomain) : primaryAdapter ? generateHandlersFromAdapter(primaryAdapter, baseUrl, capitalizedDomain) : generateDefaultHandlers(domainName, baseUrl, capitalizedDomain); return `// ${capitalizedDomain} Domain MSW Handlers // Auto-generated based on domain analysis // // ⚠️ IMPORTANT: This file is generated based on your Port interface methods. // The endpoints are generic and need to be customized to match your actual API. // // TODO: Update the endpoints and logic to match your real API: // - Modify API_BASE to your actual base URL // - Update endpoint paths for each method // - Customize request/response handling // - Add proper query parameters for search methods import { http, HttpResponse } from 'msw'; ${imports} const API_BASE = '${baseUrl}'; export const ${domainName}Handlers = [ ${handlers} ]; `; }; /** * Generate imports based on entities */ const generateImports = (entities: ParsedEntity[], mockDataPath: string, capitalizedDomain: string): string => { if (entities.length === 0) { return `import { mock${capitalizedDomain}Data } from '${mockDataPath}';`; } const entityImports = entities.map(entity => entity.name).join(', '); return `import { ${entityImports} } from '../../../domains/${entities[0]?.name.toLowerCase()}/entities'; import { mock${capitalizedDomain}Data } from '${mockDataPath}';`; }; /** * Generate handlers based on port methods (preferred approach) */ const generateHandlersFromPort = (port: ParsedPorts, baseUrl: string, capitalizedDomain: string): string => { const handlers: string[] = []; for (const method of port.methods) { const handler = generateHandlerFromMethod(method, baseUrl, capitalizedDomain); if (handler) { handlers.push(handler); } } // If no handlers generated, add default ones if (handlers.length === 0) { return generateDefaultHandlers(port.name, baseUrl, capitalizedDomain); } return handlers.join(',\n\n'); }; /** * Generate handlers based on adapter methods */ const generateHandlersFromAdapter = (adapter: ParsedAdapter, baseUrl: string, capitalizedDomain: string): string => { const handlers: string[] = []; for (const method of adapter.methods) { const handler = generateHandlerFromMethod(method, baseUrl, capitalizedDomain); if (handler) { handlers.push(handler); } } // If no handlers generated, add default ones if (handlers.length === 0) { return generateDefaultHandlers(adapter.name, baseUrl, capitalizedDomain); } return handlers.join(',\n\n'); }; /** * Generate a single handler from a method */ const generateHandlerFromMethod = (method: ParsedMethod, baseUrl: string, capitalizedDomain: string): string | null => { const { name, httpMethod, endpoint, parameters, returnType } = method; if (!httpMethod) return null; const fullEndpoint = endpoint ? `\${API_BASE}${endpoint}` : `\${API_BASE}`; const httpMethodLower = httpMethod.toLowerCase(); // Generate handler based on HTTP method switch (httpMethod) { case 'GET': return generateGetHandler(name, fullEndpoint, returnType, parameters, capitalizedDomain); case 'POST': return generatePostHandler(name, fullEndpoint, returnType, capitalizedDomain); case 'PUT': return generatePutHandler(name, fullEndpoint, returnType, capitalizedDomain); case 'DELETE': return generateDeleteHandler(name, fullEndpoint, returnType, capitalizedDomain); case 'PATCH': return generatePatchHandler(name, fullEndpoint, returnType, capitalizedDomain); default: return null; } }; /** * Generate GET handler */ const generateGetHandler = (methodName: string, endpoint: string, returnType: string, parameters: any[], capitalizedDomain: string): string => { const hasIdParam = parameters.some(p => p.name.toLowerCase().includes('id')); const isListMethod = methodName.toLowerCase().includes('list') || methodName.toLowerCase().includes('all'); if (hasIdParam && !isListMethod) { return ` // GET ${endpoint.replace('${API_BASE}', '')} - ${methodName} // TODO: Update endpoint path to match your API (e.g., '/:id', '/by-id/:id', etc.) http.get(\`${endpoint}\`, ({ params }) => { const { id } = params; const item = mock${capitalizedDomain}Data.getById(id as string); if (!item) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return HttpResponse.json(item); })`; } else { return ` // GET ${endpoint.replace('${API_BASE}', '')} - ${methodName} // TODO: Update endpoint path and add query parameters if needed http.get(\`${endpoint}\`, () => { return HttpResponse.json(mock${capitalizedDomain}Data.getAll()); })`; } }; /** * Generate POST handler */ const generatePostHandler = (methodName: string, endpoint: string, returnType: string, capitalizedDomain: string): string => { return ` // POST ${endpoint.replace('${API_BASE}', '')} - ${methodName} // TODO: Customize endpoint path and request validation http.post(\`${endpoint}\`, async ({ request }) => { try { const newItem = await request.json(); const createdItem = mock${capitalizedDomain}Data.create(newItem); return HttpResponse.json(createdItem, { status: 201 }); } catch (error) { return new HttpResponse(null, { status: 400, statusText: 'Invalid ${capitalizedDomain.toLowerCase()} data' }); } })`; }; /** * Generate PUT handler */ const generatePutHandler = (methodName: string, endpoint: string, returnType: string, capitalizedDomain: string): string => { return ` // PUT ${endpoint.replace('${API_BASE}', '')} - ${methodName} // TODO: Update endpoint path and validation logic http.put(\`${endpoint}\`, async ({ params, request }) => { const { id } = params; try { const updates = await request.json(); const updatedItem = mock${capitalizedDomain}Data.update(id as string, updates); if (!updatedItem) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return HttpResponse.json(updatedItem); } catch (error) { return new HttpResponse(null, { status: 400, statusText: 'Invalid ${capitalizedDomain.toLowerCase()} data' }); } })`; }; /** * Generate DELETE handler */ const generateDeleteHandler = (methodName: string, endpoint: string, returnType: string, capitalizedDomain: string): string => { return ` // DELETE ${endpoint.replace('${API_BASE}', '')} - ${methodName} // TODO: Customize endpoint path and authorization logic http.delete(\`${endpoint}\`, ({ params }) => { const { id } = params; const deleted = mock${capitalizedDomain}Data.delete(id as string); if (!deleted) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return new HttpResponse(null, { status: 204 }); })`; }; /** * Generate PATCH handler */ const generatePatchHandler = (methodName: string, endpoint: string, returnType: string, capitalizedDomain: string): string => { return ` // PATCH ${endpoint.replace('${API_BASE}', '')} - ${methodName} http.patch(\`${endpoint}\`, async ({ params, request }) => { const { id } = params; try { const updates = await request.json(); const updatedItem = mock${capitalizedDomain}Data.update(id as string, updates); if (!updatedItem) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return HttpResponse.json(updatedItem); } catch (error) { return new HttpResponse(null, { status: 400, statusText: 'Invalid ${capitalizedDomain.toLowerCase()} data' }); } })`; }; /** * Generate default handlers when no adapter methods found */ const generateDefaultHandlers = (domainName: string, baseUrl: string, capitalizedDomain: string): string => { return ` // GET ${baseUrl.replace('/api', '')} - List all ${domainName} items http.get(\`\${API_BASE}\`, () => { return HttpResponse.json(mock${capitalizedDomain}Data.getAll()); }), // GET ${baseUrl.replace('/api', '')}/:id - Get single ${domainName} item http.get(\`\${API_BASE}/:id\`, ({ params }) => { const { id } = params; const item = mock${capitalizedDomain}Data.getById(id as string); if (!item) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return HttpResponse.json(item); }), // POST ${baseUrl.replace('/api', '')} - Create new ${domainName} item http.post(\`\${API_BASE}\`, async ({ request }) => { try { const newItem = await request.json(); const createdItem = mock${capitalizedDomain}Data.create(newItem); return HttpResponse.json(createdItem, { status: 201 }); } catch (error) { return new HttpResponse(null, { status: 400, statusText: 'Invalid ${domainName} data' }); } }), // PUT ${baseUrl.replace('/api', '')}/:id - Update ${domainName} item http.put(\`\${API_BASE}/:id\`, async ({ params, request }) => { const { id } = params; try { const updates = await request.json(); const updatedItem = mock${capitalizedDomain}Data.update(id as string, updates); if (!updatedItem) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return HttpResponse.json(updatedItem); } catch (error) { return new HttpResponse(null, { status: 400, statusText: 'Invalid ${domainName} data' }); } }), // DELETE ${baseUrl.replace('/api', '')}/:id - Delete ${domainName} item http.delete(\`\${API_BASE}/:id\`, ({ params }) => { const { id } = params; const deleted = mock${capitalizedDomain}Data.delete(id as string); if (!deleted) { return new HttpResponse(null, { status: 404, statusText: '${capitalizedDomain} not found' }); } return new HttpResponse(null, { status: 204 }); })`; }; /** * Generate dynamic mock data based on entities */ export const generateDynamicMockData = (analysis: DomainAnalysis): string => { const { domainName, entities, primaryEntity } = analysis; const capitalizedDomain = domainName.charAt(0).toUpperCase() + domainName.slice(1); if (!primaryEntity || entities.length === 0) { // Fallback to generic template return generateGenericMockData(domainName, capitalizedDomain); } // Generate interface import const entityImports = entities.map(e => `import type { ${e.name} } from '../entities/${e.name}';`).join('\n'); const interfaceImport = entityImports; // Generate mock data with real fields const mockDataSamples = generateMockDataSamples(primaryEntity, 3); const mockDataMethods = generateMockDataMethods(primaryEntity, capitalizedDomain); return `// ${capitalizedDomain} Domain Mock Data // Auto-generated based on entity analysis // // ⚠️ IMPORTANT: This file is generated based on your Entity definitions. // The mock data structure follows your entity fields but uses generic sample values. // // TODO: Customize the mock data to match your business needs: // - Update sample data with realistic values for your domain // - Add domain-specific business logic and validation // - Customize error scenarios and edge cases // - Add more sophisticated data relationships ${interfaceImport} /** * Mock ${domainName} data store */ class Mock${capitalizedDomain}Data { private data: ${primaryEntity.name}[] = [ ${mockDataSamples} ]; ${mockDataMethods} } // Export singleton instance export const mock${capitalizedDomain}Data = new Mock${capitalizedDomain}Data(); `; }; /** * Generate mock data samples based on entity fields */ const generateMockDataSamples = (entity: ParsedEntity, count: number): string => { const samples: string[] = []; for (let i = 1; i <= count; i++) { const fields = entity.fields.map(field => { const value = generateMockValue(field.type, field.name, i); return ` ${field.name}: ${value}`; }).join(',\n'); samples.push(` {\n${fields}\n }`); } return samples.join(',\n'); }; /** * Generate mock value based on field type */ const generateMockValue = (type: string, fieldName: string, index: number): string => { const lowerFieldName = fieldName.toLowerCase(); const lowerType = type.toLowerCase(); // Handle specific field names if (lowerFieldName.includes('id')) { return `'${index}'`; } if (lowerFieldName.includes('email')) { return `'user${index}@example.com'`; } if (lowerFieldName.includes('name') || lowerFieldName.includes('title')) { return `'Sample ${fieldName} ${index}'`; } if (lowerFieldName.includes('date') || lowerFieldName.includes('time') || lowerType.includes('date')) { const date = new Date(); date.setDate(date.getDate() + index); return `new Date('${date.toISOString()}')`; } if (lowerFieldName.includes('url') || lowerFieldName.includes('link')) { return `'https://example.com/${fieldName.toLowerCase()}${index}'`; } if (lowerFieldName.includes('phone')) { return `'+1234567890${index}'`; } if (lowerFieldName.includes('price') || lowerFieldName.includes('cost') || lowerFieldName.includes('amount')) { return `${(index * 10).toFixed(2)}`; } // Handle union types (e.g., 'admin' | 'customer' | 'vendor') if (type.includes('|')) { const options = type.split('|').map(t => t.trim().replace(/['"]/g, '')); const validOptions = options.filter(opt => opt && opt.length > 0); if (validOptions.length > 0) { const selectedOption = validOptions[(index - 1) % validOptions.length]; return `'${selectedOption}'`; } } // Handle types if (lowerType.includes('string')) { return `'Sample ${fieldName} ${index}'`; } if (lowerType.includes('number')) { return `${index * 10}`; } if (lowerType.includes('boolean')) { return index % 2 === 0 ? 'true' : 'false'; } if (lowerType.includes('array')) { return `[]`; } if (lowerType.includes('object') || lowerType.includes('{')) { return `{}`; } // Default to string return `'Sample ${fieldName} ${index}'`; }; /** * Generate mock data methods */ const generateMockDataMethods = (entity: ParsedEntity, capitalizedDomain: string): string => { return ` /** * Get all ${entity.name.toLowerCase()} items */ getAll(): ${entity.name}[] { return [...this.data]; } /** * Get ${entity.name.toLowerCase()} item by ID */ getById(id: string): ${entity.name} | undefined { return this.data.find(item => item.id === id); } /** * Create new ${entity.name.toLowerCase()} item */ create(item: Partial<${entity.name}>): ${entity.name} { const newItem: ${entity.name} = { id: Date.now().toString(), ...this.getDefaultValues(), ...item, } as ${entity.name}; this.data.push(newItem); return newItem; } /** * Update ${entity.name.toLowerCase()} item */ update(id: string, updates: Partial<${entity.name}>): ${entity.name} | undefined { const index = this.data.findIndex(item => item.id === id); if (index === -1) { return undefined; } this.data[index] = { ...this.data[index], ...updates, }; return this.data[index]; } /** * Delete ${entity.name.toLowerCase()} item */ delete(id: string): boolean { const index = this.data.findIndex(item => item.id === id); if (index === -1) { return false; } this.data.splice(index, 1); return true; } /** * Reset data to initial state */ reset(): void { this.data = [ ${generateMockDataSamples(entity, 3)} ]; } /** * Get default values for new items */ private getDefaultValues(): Partial<${entity.name}> { return { ${entity.fields.filter(f => !f.optional).map(f => ` ${f.name}: ${generateMockValue(f.type, f.name, 1)}` ).join(',\n')} }; }`; }; /** * Generate generic mock data when no entities found */ const generateGenericMockData = (domainName: string, capitalizedDomain: string): string => { return `// ${capitalizedDomain} Domain Mock Data // Generic template - customize based on your domain entities // // ⚠️ IMPORTANT: No entities were found in this domain. // This is a generic template that should be customized for your needs. // // TODO: Customize this template to match your business needs: // - Replace the generic interface with your actual entity types // - Update sample data with realistic values for your domain // - Add domain-specific business logic and validation // - Import your actual entity types from ../entities/ interface ${capitalizedDomain} { id: string; name: string; createdAt: string; updatedAt: string; // Add your domain-specific fields here } /** * Mock ${domainName} data store */ class Mock${capitalizedDomain}Data { private data: ${capitalizedDomain}[] = [ { id: '1', name: 'Sample ${capitalizedDomain} 1', createdAt: '2025-01-01T00:00:00Z', updatedAt: '2025-01-01T00:00:00Z', }, { id: '2', name: 'Sample ${capitalizedDomain} 2', createdAt: '2025-01-02T00:00:00Z', updatedAt: '2025-01-02T00:00:00Z', }, { id: '3', name: 'Sample ${capitalizedDomain} 3', createdAt: '2025-01-03T00:00:00Z', updatedAt: '2025-01-03T00:00:00Z', }, ]; // Standard CRUD methods... getAll(): ${capitalizedDomain}[] { return [...this.data]; } getById(id: string): ${capitalizedDomain} | undefined { return this.data.find(item => item.id === id); } create(item: Partial<${capitalizedDomain}>): ${capitalizedDomain} { /* implementation */ } update(id: string, updates: Partial<${capitalizedDomain}>): ${capitalizedDomain} | undefined { /* implementation */ } delete(id: string): boolean { /* implementation */ } reset(): void { /* implementation */ } } // Export singleton instance export const mock${capitalizedDomain}Data = new Mock${capitalizedDomain}Data(); `; };