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
JavaScript
;
/**
* 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