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