UNPKG

nestjs-reverse-engineering

Version:

A powerful TypeScript/NestJS library for database reverse engineering, entity generation, and CRUD operations

211 lines 8.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EntityParser = void 0; /* eslint-disable prettier/prettier */ const fs = require("fs"); const path = require("path"); const glob_1 = require("glob"); class EntityParser { /** * Parse TypeORM entity files and extract table schemas */ static async parseEntityFiles(options) { const tables = []; const searchPattern = path.join(options.entitiesPath, options.filePattern); const files = await (0, glob_1.glob)(searchPattern, { ignore: ['**/index.ts', '**/*.spec.ts', '**/*.test.ts'], absolute: true }); for (const filePath of files) { const tableSchema = this.parseEntityFile(filePath); if (tableSchema) { tables.push(tableSchema); } } return tables.sort((a, b) => a.tableName.localeCompare(b.tableName)); } /** * Parse a single entity file and extract table schema */ static parseEntityFile(filePath) { try { const fileContent = fs.readFileSync(filePath, 'utf8'); // Extract table name from @Entity decorator const entityMatch = fileContent.match(/@Entity\(['"`]([^'"`]+)['"`]\)/); const tableName = entityMatch ? entityMatch[1] : this.getTableNameFromClass(fileContent); if (!tableName) { console.warn(`Could not extract table name from ${filePath}`); return null; } // Extract class name const classMatch = fileContent.match(/export\s+class\s+(\w+)/); const className = classMatch ? classMatch[1] : ''; // Parse columns from the file const columns = this.parseColumns(fileContent); // Parse primary keys const primaryKeys = columns .filter(col => col.isPrimaryKey) .map(col => col.columnName); return { tableName, tableSchema: 'public', // Default schema tableComment: this.extractTableComment(fileContent), columns, primaryKeys, foreignKeys: [], // Will be populated from column foreign key info indexes: [] }; } catch (error) { console.error(`Error parsing entity file ${filePath}:`, error); return null; } } /** * Get table name from class name if not specified in @Entity */ static getTableNameFromClass(fileContent) { const classMatch = fileContent.match(/export\s+class\s+(\w+)/); if (!classMatch) return null; // Convert PascalCase to snake_case const className = classMatch[1]; return className.replace(/([A-Z])/g, (match, char, index) => { return index === 0 ? char.toLowerCase() : `_${char.toLowerCase()}`; }); } /** * Parse columns from entity file content */ static parseColumns(fileContent) { const columns = []; // Find all property declarations with decorators - improved regex const propertyRegex = /@(Column|PrimaryGeneratedColumn|PrimaryColumn|CreateDateColumn|UpdateDateColumn|DeleteDateColumn|VersionColumn)\s*\([^)]*\)\s*\n\s*(\w+)(\?)?:\s*([^;|\n]+)/g; let match; while ((match = propertyRegex.exec(fileContent)) !== null) { const [fullMatch, decoratorType, propertyName, isOptional, tsType] = match; // Skip if propertyName is undefined if (!propertyName) { console.warn('Skipping property with undefined name:', fullMatch); continue; } // Extract column options from decorator const columnOptions = this.parseColumnOptions(fullMatch); const column = { columnName: columnOptions.name || this.camelToSnakeCase(propertyName), dataType: this.mapTsTypeToColumnType(tsType, columnOptions), isNullable: isOptional === '?' || columnOptions.nullable !== false, defaultValue: columnOptions.default, characterMaximumLength: columnOptions.length, numericPrecision: columnOptions.precision, numericScale: columnOptions.scale, columnComment: columnOptions.comment, comment: columnOptions.comment, // Alias isAutoIncrement: decoratorType === 'PrimaryGeneratedColumn', ordinalPosition: columns.length + 1, isPrimaryKey: decoratorType === 'PrimaryGeneratedColumn' || decoratorType === 'PrimaryColumn', isUnique: columnOptions.unique || false, // Handle foreign keys (would need more sophisticated parsing for relationships) foreignKeyTable: undefined, foreignKeyColumn: undefined, onDelete: undefined, onUpdate: undefined }; columns.push(column); } return columns; } /** * Parse column options from decorator string */ static parseColumnOptions(decoratorString) { const options = {}; // Extract options from decorator parameters const optionsMatch = decoratorString.match(/\(([^)]*)\)/); if (!optionsMatch) return options; const optionsStr = optionsMatch[1]; // Parse individual options (simplified parsing) const patterns = { name: /name:\s*['"`]([^'"`]+)['"`]/, type: /type:\s*['"`]([^'"`]+)['"`]/, length: /length:\s*(\d+)/, precision: /precision:\s*(\d+)/, scale: /scale:\s*(\d+)/, default: /default:\s*(?:\(\)\s*=>\s*['"`]([^'"`]+)['"`]|['"`]([^'"`]+)['"`]|(\w+))/, nullable: /nullable:\s*(true|false)/, unique: /unique:\s*(true|false)/, comment: /comment:\s*['"`]([^'"`]+)['"`]/ }; for (const [key, pattern] of Object.entries(patterns)) { const match = optionsStr.match(pattern); if (match) { if (key === 'nullable' || key === 'unique') { options[key] = match[1] === 'true'; } else if (key === 'length' || key === 'precision' || key === 'scale') { options[key] = parseInt(match[1], 10); } else if (key === 'default') { options[key] = match[1] || match[2] || match[3]; } else { options[key] = match[1]; } } } return options; } /** * Map TypeScript type to column type */ static mapTsTypeToColumnType(tsType, options) { // Remove null/undefined union types const cleanType = tsType.replace(/\s*\|\s*(null|undefined)/g, '').trim(); // Handle specific type mappings if (options.type) { return options.type; } switch (cleanType) { case 'string': if (options.length) return 'varchar'; return 'text'; case 'number': if (options.precision && options.scale) return 'decimal'; return 'int'; case 'boolean': return 'boolean'; case 'Date': return 'timestamp'; case 'Buffer': return 'bytea'; default: if (cleanType.endsWith('[]')) return 'json'; if (cleanType === 'any') return 'json'; return 'varchar'; } } /** * Convert camelCase to snake_case */ static camelToSnakeCase(str) { if (!str) return ''; return str.replace(/([A-Z])/g, (match, char, index) => { return index === 0 ? char.toLowerCase() : `_${char.toLowerCase()}`; }); } /** * Extract table comment from class comment */ static extractTableComment(fileContent) { const commentMatch = fileContent.match(/\/\*\*\s*\n\s*\*\s*([^\n]*)\s*\n\s*\*\//); return commentMatch ? commentMatch[1].trim() : undefined; } } exports.EntityParser = EntityParser; //# sourceMappingURL=entity-parser.js.map