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.

270 lines • 10.2 kB
"use strict"; /** * TypeScript type parsing service * Parses typePayload interfaces to extract field names and types */ 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.convertEmptyTypePayload = exports.generateFieldObject = exports.generateFieldDestructuring = exports.parseModuleTypes = exports.parseTypePayload = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Parses a typePayload interface from a TypeScript file */ const parseTypePayload = async (filePath) => { try { const content = await fs.promises.readFile(filePath, 'utf-8'); // Find the typePayload interface with proper brace matching const interfaceStart = content.indexOf('export type typePayload = {'); if (interfaceStart === -1) { return { fields: [], hasId: false, hasPagination: false }; } // Find the matching closing brace const startBrace = content.indexOf('{', interfaceStart); let braceCount = 1; let endBrace = startBrace + 1; while (endBrace < content.length && braceCount > 0) { if (content[endBrace] === '{') braceCount++; if (content[endBrace] === '}') braceCount--; endBrace++; } if (braceCount !== 0) { return { fields: [], hasId: false, hasPagination: false }; } const interfaceBody = content.substring(startBrace + 1, endBrace - 1); const fields = []; let hasId = false; let hasPagination = false; // Parse fields with support for nested objects const parsedFields = parseInterfaceFields(interfaceBody); for (const field of parsedFields) { fields.push(field); // Check for special fields if (field.name === 'id') hasId = true; if (['page', 'limit', 'sort_by', 'sort_order'].includes(field.name)) { hasPagination = true; } } // Check if typePayload is empty (no fields defined) const isEmpty = fields.length === 0; return { fields, hasId, hasPagination, isEmpty }; } catch (error) { console.error(`Error parsing type file ${filePath}:`, error); return { fields: [], hasId: false, hasPagination: false }; } }; exports.parseTypePayload = parseTypePayload; /** * Parses all typePayload files in a module's types directory */ const parseModuleTypes = async (modulePath) => { const typesDir = path.join(modulePath, 'types'); const result = {}; try { const files = await fs.promises.readdir(typesDir); for (const file of files) { if (file.endsWith('.ts')) { const filePath = path.join(typesDir, file); const operationName = file.replace(/\.[^.]+\.ts$/, ''); // Extract operation name (create, get, etc.) result[operationName] = await (0, exports.parseTypePayload)(filePath); } } } catch (error) { console.error(`Error reading types directory ${typesDir}:`, error); } return result; }; exports.parseModuleTypes = parseModuleTypes; /** * Parses interface fields with support for nested objects */ const parseInterfaceFields = (interfaceBody) => { const fields = []; let i = 0; const lines = interfaceBody.split('\n'); while (i < lines.length) { const line = lines[i].trim(); // Skip empty lines and comments if (!line || line.startsWith('//') || line.startsWith('*')) { i++; continue; } // Check if this line starts a nested object if (line.includes(': {')) { const field = parseNestedObjectField(lines, i); if (field) { fields.push(field.field); i = field.nextIndex; continue; } } // Parse regular field const fieldMatch = line.match(/^(\w+)(\?)?:\s*([^;]+);?/); if (fieldMatch) { const [, name, optionalMarker, type] = fieldMatch; // Extract default value if present let defaultValue; const defaultMatch = type.match(/^([^=]+)\s*=\s*(.+)$/); let cleanType = type.trim(); if (defaultMatch) { cleanType = defaultMatch[1].trim(); defaultValue = defaultMatch[2].trim(); } // Check if field is optional using different syntaxes const isOptionalMarker = !!optionalMarker; const isUnionWithUndefined = cleanType.includes('| undefined') || cleanType.includes('|undefined'); const hasDefaultValue = !!defaultValue; const isOptional = isOptionalMarker || isUnionWithUndefined || hasDefaultValue; // Clean up union with undefined syntax if (isUnionWithUndefined) { cleanType = cleanType.replace(/\s*\|\s*undefined/g, '').trim(); } fields.push({ name, type: cleanType, optional: isOptional, ...(defaultValue && { defaultValue }), }); } i++; } return fields; }; /** * Parses a nested object field starting from a specific line */ const parseNestedObjectField = (lines, startIndex) => { const startLine = lines[startIndex].trim(); const fieldMatch = startLine.match(/^(\w+)(\?)?:\s*\{/); if (!fieldMatch) return null; const [, name, optionalMarker] = fieldMatch; const isOptional = !!optionalMarker; // Find the closing brace let braceCount = 1; let endIndex = startIndex + 1; const nestedLines = []; while (endIndex < lines.length && braceCount > 0) { const line = lines[endIndex].trim(); if (line.includes('{')) braceCount++; if (line.includes('}')) braceCount--; if (braceCount > 0) { nestedLines.push(line); } endIndex++; } // Parse nested fields const nestedFields = parseInterfaceFields(nestedLines.join('\n')); return { field: { name, type: 'object', optional: isOptional, isNested: true, nestedFields, }, nextIndex: endIndex, }; }; /** * Generates field destructuring pattern for function parameters */ const generateFieldDestructuring = (fields) => { if (fields.length === 0) { return '// No fields defined in typePayload'; } const fieldLines = fields.map(field => { if (field.defaultValue) { return ` ${field.name} = ${field.defaultValue},`; } // Handle nested objects if (field.isNested && field.nestedFields && field.nestedFields.length > 0) { const nestedDestructuring = field.nestedFields.map(nf => nf.name).join(', '); return ` ${field.name}: { ${nestedDestructuring} },`; } return ` ${field.name},`; }); return fieldLines.join('\n'); }; exports.generateFieldDestructuring = generateFieldDestructuring; /** * Generates field object for passing to repository functions */ const generateFieldObject = (fields, excludeFields = []) => { const filteredFields = fields.filter(field => !excludeFields.includes(field.name)); if (filteredFields.length === 0) { return '// No fields to pass'; } const fieldLines = filteredFields.map(field => { // Handle nested objects if (field.isNested && field.nestedFields && field.nestedFields.length > 0) { const nestedFields = field.nestedFields.map(nf => `${nf.name}`).join(', '); return ` ${field.name}: { ${nestedFields} },`; } return ` ${field.name},`; }); return fieldLines.join('\n'); }; exports.generateFieldObject = generateFieldObject; /** * Converts empty typePayload from {} to Record<string, never> */ const convertEmptyTypePayload = async (filePath) => { try { const content = await fs.promises.readFile(filePath, 'utf-8'); // Check if typePayload is empty {} const emptyPattern = /export type typePayload = \{\s*\};?/; if (emptyPattern.test(content)) { // Replace with Record<string, never> const updatedContent = content.replace(emptyPattern, 'export type typePayload = Record<string, never>;'); await fs.promises.writeFile(filePath, updatedContent, 'utf-8'); return true; } return false; } catch (error) { console.error(`Error converting empty typePayload in ${filePath}:`, error); return false; } }; exports.convertEmptyTypePayload = convertEmptyTypePayload; //# sourceMappingURL=type-parser.service.js.map