UNPKG

nestjs-reverse-engineering

Version:

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

268 lines 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EntityBuilder = void 0; /* eslint-disable prettier/prettier */ const file_builder_1 = require("./file-builder"); const type_mapper_1 = require("../utils/type-mapper"); const naming_utils_1 = require("../utils/naming-utils"); const path = require("path"); class EntityBuilder { constructor(options, allTables, dialect) { this.options = options; this.allTables = allTables; this.dialect = dialect; } /** * Generate entity class for a table */ async generateEntity(table) { const fileBuilder = new file_builder_1.FileBuilder({ outputPath: this.getEntityFilePath(table.tableName), overwrite: true, createDirectories: true }); const className = naming_utils_1.NamingUtils.toSafeClassName(table.tableName); const imports = this.generateImports(table); // Add file header comment fileBuilder.addComment('Generated by reverse-engineering tool', 'line'); fileBuilder.addEmptyLine(); // Add imports imports.forEach(importStatement => { fileBuilder.addLine(importStatement); }); fileBuilder.addEmptyLine(); // Add table comment if available if (table.tableComment && this.options.includeComments) { fileBuilder.addComment(table.tableComment, 'block'); } // Add entity decorator const tableDecorator = this.generateTableDecorator(table); fileBuilder.addLine(tableDecorator); // Start class fileBuilder.startBlock(`export class ${className}`); // Generate properties for (const column of table.columns) { await this.generateProperty(fileBuilder, column, table); fileBuilder.addEmptyLine(); } // Generate relationships if enabled if (this.options.includeRelations) { await this.generateRelationships(fileBuilder, table); } // End class fileBuilder.endBlock(); // Write to file await fileBuilder.writeToFile(); console.log(`Generated entity: ${className} -> ${this.getEntityFilePath(table.tableName)}`); } generateImports(table) { const imports = []; const typeOrmImports = new Set(['Entity', 'Column']); const entityImports = new Set(); // Check if we need PrimaryGeneratedColumn, PrimaryColumn, or composite primary key decorators const primaryKeys = table.primaryKeys; if (primaryKeys.length === 1) { const pkColumn = table.columns.find(col => col.columnName === primaryKeys[0]); if (pkColumn?.isAutoIncrement) { typeOrmImports.add('PrimaryGeneratedColumn'); } else { typeOrmImports.add('PrimaryColumn'); } } else if (primaryKeys.length > 1) { typeOrmImports.add('PrimaryColumn'); } // Check for relationships if (this.options.includeRelations && table.foreignKeys.length > 0) { typeOrmImports.add('ManyToOne'); typeOrmImports.add('JoinColumn'); // Add imports for referenced entities table.foreignKeys.forEach(fk => { const referencedClassName = naming_utils_1.NamingUtils.toSafeClassName(fk.referencedTableName); const fileName = naming_utils_1.NamingUtils.toFileName(fk.referencedTableName); entityImports.add(`import { ${referencedClassName} } from './${fileName}.entity';`); }); } // Check for reverse relationships const reverseRelations = this.findReverseRelations(table); if (this.options.includeRelations && reverseRelations.length > 0) { typeOrmImports.add('OneToMany'); // Add imports for related entities reverseRelations.forEach(relation => { const relatedClassName = naming_utils_1.NamingUtils.toSafeClassName(relation.tableName); const fileName = naming_utils_1.NamingUtils.toFileName(relation.tableName); entityImports.add(`import { ${relatedClassName} } from './${fileName}.entity';`); }); } // Check for enum types const hasEnums = table.columns.some(col => col.enumValues && col.enumValues.length > 0); if (hasEnums) { // We'll generate enums inline or import them } imports.push(`import { ${Array.from(typeOrmImports).sort().join(', ')} } from 'typeorm';`); // Add entity imports Array.from(entityImports).sort().forEach(entityImport => { imports.push(entityImport); }); return imports; } generateTableDecorator(table) { const nameInfo = naming_utils_1.NamingUtils.handleCompositeName(table.tableName); if (nameInfo.quoted || table.tableSchema !== 'public') { return `@Entity({ name: '${table.tableName}', schema: '${table.tableSchema}' })`; } else { return `@Entity('${table.tableName}')`; } } async generateProperty(fileBuilder, column, table) { const propertyName = naming_utils_1.NamingUtils.toSafePropertyName(column.columnName, this.options.namingConvention); const typeMapping = type_mapper_1.TypeMapper.mapType(column.dataType, this.dialect, column.isNullable); const isPrimaryKey = table.primaryKeys.includes(column.columnName); // Add comment if enabled if (column.columnComment && this.options.includeComments) { fileBuilder.addComment(column.columnComment); } const decorators = []; // Primary key decorators if (isPrimaryKey) { if (column.isAutoIncrement && table.primaryKeys.length === 1) { decorators.push('@PrimaryGeneratedColumn()'); } else { decorators.push('@PrimaryColumn()'); } } else { // Regular column decorator const columnOptions = this.generateColumnOptions(column, typeMapping); decorators.push(`@Column(${columnOptions})`); } // Generate property fileBuilder.addProperty(propertyName, typeMapping.tsType + (typeMapping.isOptional ? ' | null' : ''), { decorators, optional: typeMapping.isOptional, comment: column.columnComment && this.options.includeComments ? column.columnComment : undefined }); } generateColumnOptions(column, typeMapping) { const options = {}; // Handle column name mismatch const propertyName = naming_utils_1.NamingUtils.toSafePropertyName(column.columnName, this.options.namingConvention); const nameInfo = naming_utils_1.NamingUtils.handleCompositeName(column.columnName); if (nameInfo.quoted || nameInfo.safe !== column.columnName || propertyName !== column.columnName) { options.name = `'${column.columnName}'`; } // Type mapping if (typeMapping.typeormType !== 'varchar' || typeMapping.typeormType === 'text') { options.type = `'${typeMapping.typeormType}'`; } // Length if (column.characterMaximumLength && typeMapping.typeormType === 'varchar') { options.length = column.characterMaximumLength; } // Precision and scale for numeric types if (column.numericPrecision && ['decimal', 'numeric'].includes(typeMapping.typeormType)) { options.precision = column.numericPrecision; if (column.numericScale) { options.scale = column.numericScale; } } // Nullable if (!column.isNullable) { options.nullable = false; } // Default value if (column.defaultValue && !column.isAutoIncrement) { // Parse and format default values const defaultVal = column.defaultValue; // Handle common default value patterns if (defaultVal === 'CURRENT_TIMESTAMP' || defaultVal === 'now()') { options.default = '() => "CURRENT_TIMESTAMP"'; } else if (defaultVal.includes('nextval')) { // Skip sequence defaults as they're handled by @PrimaryGeneratedColumn } else if (!isNaN(Number(defaultVal))) { options.default = Number(defaultVal); } else if (defaultVal.startsWith("'") && defaultVal.endsWith("'")) { options.default = defaultVal; } else { options.default = `'${defaultVal}'`; } } // Enum values if (column.enumValues && column.enumValues.length > 0) { options.enum = `[${column.enumValues.map(val => `'${val}'`).join(', ')}]`; } // Comment if (column.columnComment && this.options.includeComments) { options.comment = `'${column.columnComment.replace(/'/g, "\\'")}'`; } // Format options object if (Object.keys(options).length === 0) { return ''; } const optionStrings = Object.entries(options).map(([key, value]) => { if (typeof value === 'string' && (key === 'default' && value.startsWith('()'))) { return `${key}: ${value}`; } return `${key}: ${value}`; }); return `{ ${optionStrings.join(', ')} }`; } async generateRelationships(fileBuilder, table) { for (const fk of table.foreignKeys) { await this.generateManyToOneRelation(fileBuilder, fk); fileBuilder.addEmptyLine(); } // Generate reverse relationships (OneToMany) const reverseRelations = this.findReverseRelations(table); for (const relation of reverseRelations) { await this.generateOneToManyRelation(fileBuilder, relation, table); fileBuilder.addEmptyLine(); } } async generateManyToOneRelation(fileBuilder, fk) { const referencedTable = this.allTables.find(t => t.tableName === fk.referencedTableName && t.tableSchema === fk.referencedTableSchema); if (!referencedTable) return; const referencedClassName = naming_utils_1.NamingUtils.toSafeClassName(fk.referencedTableName); const propertyName = naming_utils_1.NamingUtils.toRelationshipName(fk.referencedTableName, false); fileBuilder.addLine(`@ManyToOne(() => ${referencedClassName})`); fileBuilder.addLine(`@JoinColumn({ name: '${fk.columnName}' })`); fileBuilder.addProperty(propertyName, referencedClassName, { optional: true }); } async generateOneToManyRelation(fileBuilder, relation, table) { const relatedClassName = naming_utils_1.NamingUtils.toSafeClassName(relation.tableName); const propertyName = naming_utils_1.NamingUtils.toRelationshipName(relation.tableName, true); fileBuilder.addLine(`@OneToMany(() => ${relatedClassName}, ${relation.propertyName} => ${relation.propertyName}.${naming_utils_1.NamingUtils.toRelationshipName(table.tableName, false)})`); fileBuilder.addProperty(propertyName, `${relatedClassName}[]`, { optional: true }); } findReverseRelations(table) { const reverseRelations = []; for (const otherTable of this.allTables) { if (otherTable.tableName === table.tableName) continue; for (const fk of otherTable.foreignKeys) { if (fk.referencedTableName === table.tableName && fk.referencedTableSchema === table.tableSchema) { reverseRelations.push({ tableName: otherTable.tableName, foreignKey: fk, propertyName: naming_utils_1.NamingUtils.toRelationshipName(otherTable.tableName, false) }); } } } return reverseRelations; } getEntityFilePath(tableName) { const fileName = naming_utils_1.NamingUtils.toFileName(tableName); return path.join(this.options.outputPath, `${fileName}.entity.ts`); } } exports.EntityBuilder = EntityBuilder; //# sourceMappingURL=entity-builder.js.map