UNPKG

node-apis

Version:

🚀 Advanced TypeScript API generator with clean architecture, comprehensive testing, and automatic formatting. Generate production-ready Node.js APIs with complete integration test suites.

550 lines • 30 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateCodeWithParsedTypes = exports.generateTypeFilesOnly = void 0; const path = __importStar(require("path")); const file_operations_1 = require("../filesystem/file.operations"); const directory_operations_1 = require("../filesystem/directory.operations"); const crud_templates_1 = require("../templates/crud.templates"); const custom_templates_1 = require("../templates/custom.templates"); const services_templates_1 = require("../templates/services.templates"); const type_parser_service_1 = require("./type-parser.service"); const typed_repository_templates_1 = require("../templates/typed-repository.templates"); const typed_crud_validators_1 = require("../templates/typed-crud.validators"); const custom_validators_1 = require("../templates/custom.validators"); const typed_custom_validators_1 = require("../templates/typed-custom.validators"); const crud_controllers_1 = require("../templates/crud.controllers"); const custom_controllers_1 = require("../templates/custom.controllers"); const trpc_procedures_1 = require("../templates/trpc.procedures"); const trpc_router_1 = require("../templates/trpc.router"); const t3_procedures_1 = require("../templates/t3.procedures"); const t3_router_1 = require("../templates/t3.router"); const t3_constants_1 = require("../templates/t3.constants"); const t3_logger_1 = require("../templates/t3.logger"); const t3_types_1 = require("../templates/t3.types"); const typed_crud_handlers_1 = require("../templates/typed-crud.handlers"); const typed_custom_handlers_1 = require("../templates/typed-custom.handlers"); const routes_templates_1 = require("../templates/routes.templates"); const formatter_service_1 = require("./formatter.service"); const generateTypeFilesOnly = async ({ moduleName, modulePath, apiType, appendMode = false, framework = 'express', }) => { const generatedFiles = []; const typesDir = path.join(modulePath, 'types'); if (apiType.type === 'crud') { // Use T3-specific type templates for T3 framework if (framework === 't3') { const crudFileNames = (0, t3_types_1.getT3CrudTypeFileNames)({ moduleName }); const crudOperations = ['create', 'get', 'list', 'delete', 'update']; for (let i = 0; i < crudFileNames.length; i++) { const fileName = crudFileNames[i]; const operation = crudOperations[i]; // Generate T3 type file (only typePayload and typeResult) const typeFilePath = path.join(typesDir, fileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: typeFilePath }))) { const typeContent = (0, t3_types_1.generateT3CrudTypeContent)({ operation, moduleName }); await (0, file_operations_1.writeFile)({ filePath: typeFilePath, content: typeContent }); generatedFiles.push({ fileName, filePath: typeFilePath, content: typeContent }); } } } else { // Use standard type templates for Express/Hono const crudFileNames = (0, crud_templates_1.getCrudFileNames)({ moduleName }); const crudOperations = ['create', 'get', 'list', 'delete', 'update']; for (let i = 0; i < crudFileNames.length; i++) { const fileName = crudFileNames[i]; const operation = crudOperations[i]; // Generate type file const typeFilePath = path.join(typesDir, fileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: typeFilePath }))) { const typeContent = (0, crud_templates_1.generateCrudFileContent)({ operation, moduleName }); await (0, file_operations_1.writeFile)({ filePath: typeFilePath, content: typeContent }); generatedFiles.push({ fileName, filePath: typeFilePath, content: typeContent }); } } } } else if (apiType.type === 'custom' && apiType.customNames) { // Use T3-specific type templates for T3 framework if (framework === 't3') { const customFileNames = (0, t3_types_1.getT3CustomTypeFileNames)({ customNames: apiType.customNames, moduleName, }); for (let i = 0; i < customFileNames.length; i++) { const fileName = customFileNames[i]; const customName = apiType.customNames[i]; // Generate T3 custom type file (only typePayload and typeResult) const typeFilePath = path.join(typesDir, fileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: typeFilePath }))) { const typeContent = (0, t3_types_1.generateT3CustomTypeContent)({ customName, moduleName }); await (0, file_operations_1.writeFile)({ filePath: typeFilePath, content: typeContent }); generatedFiles.push({ fileName, filePath: typeFilePath, content: typeContent }); } } } else { // Use standard type templates for Express/Hono const customFileNames = (0, custom_templates_1.getCustomFileNames)({ customNames: apiType.customNames, moduleName, }); for (let i = 0; i < customFileNames.length; i++) { const fileName = customFileNames[i]; const customName = apiType.customNames[i]; // Generate type file const typeFilePath = path.join(typesDir, fileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: typeFilePath }))) { const typeContent = (0, custom_templates_1.generateCustomFileContent)({ customName, moduleName }); await (0, file_operations_1.writeFile)({ filePath: typeFilePath, content: typeContent }); generatedFiles.push({ fileName, filePath: typeFilePath, content: typeContent }); } } } } else if (apiType.type === 'services' && apiType.serviceNames) { const serviceFileNames = (0, services_templates_1.getServiceFileNames)({ moduleName, serviceNames: apiType.serviceNames, }); for (let i = 0; i < serviceFileNames.length; i++) { const fileName = serviceFileNames[i]; const serviceName = apiType.serviceNames[i]; // Generate type file const typeFilePath = path.join(typesDir, fileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: typeFilePath }))) { const typeContent = (0, services_templates_1.generateServiceTypeContent)({ serviceName, moduleName }); await (0, file_operations_1.writeFile)({ filePath: typeFilePath, content: typeContent }); generatedFiles.push({ fileName, filePath: typeFilePath, content: typeContent }); } } } return generatedFiles; }; exports.generateTypeFilesOnly = generateTypeFilesOnly; const generateCodeWithParsedTypes = async ({ moduleName, modulePath, apiType, framework = 'express', appendMode = false, trpcStyle = false, }) => { const generatedFiles = []; const validatorsDir = path.join(modulePath, 'validators'); const controllersDir = path.join(modulePath, 'controllers'); const proceduresDir = path.join(modulePath, 'procedures'); const handlersDir = path.join(modulePath, 'handlers'); const repositoryDir = path.join(modulePath, 'repository'); const typesDir = path.join(modulePath, 'types'); // Parse the type files to get actual field names const parsedTypes = await (0, type_parser_service_1.parseModuleTypes)(modulePath); // Convert empty typePayloads to Record<string, never> await Promise.all(Object.entries(parsedTypes).map(async ([operation, parsedType]) => { if (parsedType.isEmpty) { const typeFilePath = path.join(typesDir, `${operation}.${moduleName}.ts`); await (0, type_parser_service_1.convertEmptyTypePayload)(typeFilePath); } })); if (apiType.type === 'crud') { const crudValidatorFileNames = (0, typed_crud_validators_1.getCrudValidatorFileNames)({ moduleName }); const crudControllerFileNames = (0, crud_controllers_1.getCrudControllerFileNames)({ moduleName }); const crudProcedureFileNames = (0, trpc_procedures_1.getTrpcProcedureFileNames)({ moduleName }); const crudHandlerFileNames = (0, typed_crud_handlers_1.getCrudHandlerFileNames)({ moduleName }); const crudRepositoryFileNames = (0, typed_repository_templates_1.getCrudRepositoryFileNames)({ moduleName }); // const crudServiceFileNames = getCrudServiceFileNames({ moduleName }); // Removed - no longer using service layer const crudOperations = ['create', 'get', 'list', 'delete', 'update']; for (let i = 0; i < crudOperations.length; i++) { const validatorFileName = crudValidatorFileNames[i]; const controllerFileName = crudControllerFileNames[i]; const procedureFileName = crudProcedureFileNames[i]; const handlerFileName = crudHandlerFileNames[i]; const repositoryFileName = crudRepositoryFileNames[i]; // const serviceFileName = crudServiceFileNames[i]; // Removed - no longer using service layer const operation = crudOperations[i]; const parsedType = parsedTypes[operation] || { fields: [], hasId: false, hasPagination: false, }; // Generate validator file (skip if typePayload is empty) if (!parsedType.isEmpty) { const validatorFilePath = path.join(validatorsDir, validatorFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: validatorFilePath }))) { const validatorContent = (0, typed_crud_validators_1.generateCrudValidatorContent)({ operation, moduleName, parsedType, }); await (0, file_operations_1.writeFile)({ filePath: validatorFilePath, content: validatorContent }); generatedFiles.push({ fileName: validatorFileName, filePath: validatorFilePath, content: validatorContent, }); } } // Generate controller or procedure file based on style if (framework === 't3') { // Generate T3 procedure file const procedureFilePath = path.join(proceduresDir, procedureFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: procedureFilePath }))) { const procedureContent = (0, t3_procedures_1.generateT3ProcedureContent)({ operation, moduleName, }); await (0, file_operations_1.writeFile)({ filePath: procedureFilePath, content: procedureContent }); generatedFiles.push({ fileName: procedureFileName, filePath: procedureFilePath, content: procedureContent, }); } } else if (trpcStyle) { // Generate tRPC procedure file const procedureFilePath = path.join(proceduresDir, procedureFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: procedureFilePath }))) { const procedureContent = (0, trpc_procedures_1.generateTrpcProcedureContent)({ operation, moduleName, }); await (0, file_operations_1.writeFile)({ filePath: procedureFilePath, content: procedureContent }); generatedFiles.push({ fileName: procedureFileName, filePath: procedureFilePath, content: procedureContent, }); } } else { // Generate REST controller file const controllerFilePath = path.join(controllersDir, controllerFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: controllerFilePath }))) { const controllerContent = (0, crud_controllers_1.generateCrudControllerContent)({ operation, moduleName, framework, }); await (0, file_operations_1.writeFile)({ filePath: controllerFilePath, content: controllerContent }); generatedFiles.push({ fileName: controllerFileName, filePath: controllerFilePath, content: controllerContent, }); } } // Generate handler file with parsed types (contains business logic) const handlerFilePath = path.join(handlersDir, handlerFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: handlerFilePath }))) { const handlerContent = (0, typed_crud_handlers_1.generateCrudHandlerContent)({ operation, moduleName, parsedType }); await (0, file_operations_1.writeFile)({ filePath: handlerFilePath, content: handlerContent }); generatedFiles.push({ fileName: handlerFileName, filePath: handlerFilePath, content: handlerContent, }); } // Generate individual repository file per operation const repositoryFilePath = path.join(repositoryDir, repositoryFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: repositoryFilePath }))) { const repositoryContent = (0, typed_repository_templates_1.generateCrudRepositoryContent)({ operation, moduleName, parsedType, }); await (0, file_operations_1.writeFile)({ filePath: repositoryFilePath, content: repositoryContent }); generatedFiles.push({ fileName: repositoryFileName, filePath: repositoryFilePath, content: repositoryContent, }); } // Skip service generation - business logic is now in handlers } } else if (apiType.type === 'custom' && apiType.customNames) { const customValidatorFileNames = (0, custom_validators_1.getCustomValidatorFileNames)({ customNames: apiType.customNames, moduleName, }); const customControllerFileNames = (0, custom_controllers_1.getCustomControllerFileNames)({ customNames: apiType.customNames, moduleName, }); for (let i = 0; i < apiType.customNames.length; i++) { const validatorFileName = customValidatorFileNames[i]; const controllerFileName = customControllerFileNames[i]; const customName = apiType.customNames[i]; // Generate procedure file name for custom operations const procedureFileName = `${customName}.${moduleName}.ts`; // Generate validator file with parsed types (skip if typePayload is empty) const parsedType = parsedTypes[customName] || { fields: [], hasId: false, hasPagination: false }; if (!parsedType.isEmpty) { const validatorFilePath = path.join(validatorsDir, validatorFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: validatorFilePath }))) { const validatorContent = (0, typed_custom_validators_1.generateTypedCustomValidatorContent)({ customName, moduleName, parsedType }); await (0, file_operations_1.writeFile)({ filePath: validatorFilePath, content: validatorContent }); generatedFiles.push({ fileName: validatorFileName, filePath: validatorFilePath, content: validatorContent, }); } } // Generate controller or procedure file based on framework if (framework === 't3') { // Generate T3 procedure file const procedureFilePath = path.join(proceduresDir, procedureFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: procedureFilePath }))) { const procedureContent = (0, t3_procedures_1.generateT3ProcedureContent)({ operation: customName, moduleName, }); await (0, file_operations_1.writeFile)({ filePath: procedureFilePath, content: procedureContent }); generatedFiles.push({ fileName: procedureFileName, filePath: procedureFilePath, content: procedureContent, }); } } else { // Generate REST controller file const controllerFilePath = path.join(controllersDir, controllerFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: controllerFilePath }))) { const controllerContent = (0, custom_controllers_1.generateCustomControllerContent)({ customName, moduleName, framework, }); await (0, file_operations_1.writeFile)({ filePath: controllerFilePath, content: controllerContent }); generatedFiles.push({ fileName: controllerFileName, filePath: controllerFilePath, content: controllerContent, }); } } // Generate handler file const handlerFileName = `${customName}.${moduleName}.ts`; const handlerFilePath = path.join(handlersDir, handlerFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: handlerFilePath }))) { const parsedType = parsedTypes[customName] || { fields: [], hasId: false, hasPagination: false }; const handlerContent = (0, typed_custom_handlers_1.generateTypedCustomHandlerContent)({ customName, moduleName, parsedType, }); await (0, file_operations_1.writeFile)({ filePath: handlerFilePath, content: handlerContent }); generatedFiles.push({ fileName: handlerFileName, filePath: handlerFilePath, content: handlerContent, }); } // Generate individual repository file per custom operation const repositoryFileName = `${customName}.${moduleName}.ts`; const repositoryFilePath = path.join(repositoryDir, repositoryFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: repositoryFilePath }))) { const parsedType = parsedTypes[customName] || { fields: [], hasId: false, hasPagination: false }; const repositoryContent = (0, typed_repository_templates_1.generateCustomRepositoryContent)({ customName, moduleName, parsedType, }); await (0, file_operations_1.writeFile)({ filePath: repositoryFilePath, content: repositoryContent }); generatedFiles.push({ fileName: repositoryFileName, filePath: repositoryFilePath, content: repositoryContent, }); } } } else if (apiType.type === 'services' && apiType.serviceNames) { const servicesDir = path.join(modulePath, 'services'); const serviceFileNames = (0, services_templates_1.getServiceFileNames)({ moduleName, serviceNames: apiType.serviceNames, }); for (let i = 0; i < apiType.serviceNames.length; i++) { const serviceFileName = serviceFileNames[i]; const serviceName = apiType.serviceNames[i]; // Generate service file const serviceFilePath = path.join(servicesDir, serviceFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: serviceFilePath }))) { const serviceContent = (0, services_templates_1.generateServiceContent)({ serviceName, moduleName }); await (0, file_operations_1.writeFile)({ filePath: serviceFilePath, content: serviceContent }); generatedFiles.push({ fileName: serviceFileName, filePath: serviceFilePath, content: serviceContent, }); } } // Skip routes generation for services (they are internal) // Format all generated files const filePaths = generatedFiles.map(file => file.filePath); await (0, formatter_service_1.formatGeneratedFiles)(filePaths); return generatedFiles; } // Generate routes or router file based on style if (framework === 't3') { // Generate T3 utility files (constants and logger) - only once const constantsDir = path.join(modulePath, '..', '..', 'constants'); const utilsDir = path.join(modulePath, '..', '..', 'utils'); await (0, directory_operations_1.ensureDirectory)({ dirPath: constantsDir }); await (0, directory_operations_1.ensureDirectory)({ dirPath: utilsDir }); // Generate error constants file if it doesn't exist const constantsFileName = (0, t3_constants_1.getErrorConstantsFileName)(); const constantsFilePath = path.join(constantsDir, constantsFileName); if (!(await (0, file_operations_1.fileExists)({ filePath: constantsFilePath }))) { const constantsContent = (0, t3_constants_1.generateErrorConstantsContent)(); await (0, file_operations_1.writeFile)({ filePath: constantsFilePath, content: constantsContent }); generatedFiles.push({ fileName: constantsFileName, filePath: constantsFilePath, content: constantsContent, }); } // Generate logger file if it doesn't exist const loggerFileName = (0, t3_logger_1.getLoggerFileName)(); const loggerFilePath = path.join(utilsDir, loggerFileName); if (!(await (0, file_operations_1.fileExists)({ filePath: loggerFilePath }))) { const loggerContent = (0, t3_logger_1.generateLoggerContent)(); await (0, file_operations_1.writeFile)({ filePath: loggerFilePath, content: loggerContent }); generatedFiles.push({ fileName: loggerFileName, filePath: loggerFilePath, content: loggerContent, }); } // Generate T3 router file with automatic operation merging const routerFileName = `${moduleName}.ts`; const routersDir = path.join(modulePath, '..', 'routers'); const routerFilePath = path.join(routersDir, routerFileName); // Ensure routers directory exists await (0, directory_operations_1.ensureDirectory)({ dirPath: routersDir }); // Determine new operations to add let newOperations = []; if (apiType.type === 'crud') { newOperations = ['create', 'get', 'list', 'update', 'delete']; } else if (apiType.type === 'custom' && apiType.customNames) { newOperations = apiType.customNames; } else if (apiType.type === 'services' && apiType.serviceNames) { newOperations = apiType.serviceNames; } // Check if router file already exists const routerExists = await (0, file_operations_1.fileExists)({ filePath: routerFilePath }); let existingOperations = []; if (routerExists) { try { const existingContent = await (0, file_operations_1.readFile)({ filePath: routerFilePath }); existingOperations = (0, t3_router_1.parseT3RouterOperations)(existingContent); } catch (error) { // If parsing fails, continue with just new operations existingOperations = []; } } // Merge existing and new operations (removes duplicates) const allOperations = [...new Set([...existingOperations, ...newOperations])]; // Generate router with merged operations const routerContent = (0, t3_router_1.generateMergedT3RouterContent)({ moduleName, operations: allOperations, }); await (0, file_operations_1.writeFile)({ filePath: routerFilePath, content: routerContent }); generatedFiles.push({ fileName: routerFileName, filePath: routerFilePath, content: routerContent, }); } else if (trpcStyle) { // Generate tRPC router file const routerFileName = `${moduleName}.router.ts`; const routerFilePath = path.join(modulePath, routerFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: routerFilePath }))) { let routerContent; if (apiType.type === 'crud') { routerContent = (0, trpc_router_1.generateTrpcRouterContent)({ moduleName, operations: ['create', 'get', 'list', 'update', 'delete'] }); } else if (apiType.type === 'custom' && apiType.customNames) { routerContent = (0, trpc_router_1.generateCustomTrpcRouterContent)({ moduleName, operations: apiType.customNames }); } else if (apiType.type === 'services' && apiType.serviceNames) { routerContent = (0, trpc_router_1.generateServicesTrpcRouterContent)({ moduleName, operations: apiType.serviceNames }); } else { routerContent = (0, trpc_router_1.generateTrpcRouterContent)({ moduleName }); } await (0, file_operations_1.writeFile)({ filePath: routerFilePath, content: routerContent }); generatedFiles.push({ fileName: routerFileName, filePath: routerFilePath, content: routerContent, }); } } else { // Generate REST routes file const routesFileName = `${moduleName}.routes.ts`; const routesFilePath = path.join(modulePath, routesFileName); if (!appendMode || !(await (0, file_operations_1.fileExists)({ filePath: routesFilePath }))) { const routesContent = (0, routes_templates_1.generateRouteContent)({ moduleName, apiType, framework }); await (0, file_operations_1.writeFile)({ filePath: routesFilePath, content: routesContent }); generatedFiles.push({ fileName: routesFileName, filePath: routesFilePath, content: routesContent, }); } } // Format all generated files const filePaths = generatedFiles.map(file => file.filePath); await (0, formatter_service_1.formatGeneratedFiles)(filePaths); return generatedFiles; }; exports.generateCodeWithParsedTypes = generateCodeWithParsedTypes; //# sourceMappingURL=two-phase-generator.service.js.map