UNPKG

@kenniy/godeye-data-contracts

Version:

Enterprise-grade base repository architecture for GOD-EYE microservices with zero overhead and maximum code reuse

252 lines (251 loc) 10.8 kB
"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.RepositoryGenerator = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const Handlebars = __importStar(require("handlebars")); class RepositoryGenerator { constructor() { this.templates = new Map(); this.loadTemplates(); this.registerHelpers(); } /** * Auto-detect ORM type from current service directory */ detectORM(servicePath = process.cwd()) { const packageJsonPath = path.join(servicePath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { throw new Error('package.json not found. Run this command from a GOD-EYE service root directory.'); } const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for TypeORM (PostgreSQL services) if (dependencies.typeorm) { return { type: 'typeorm', entityPath: 'src/libs/dal/entities', repositoryPath: 'src/libs/dal/repositories' }; } // Check for Mongoose (MongoDB services) if (dependencies.mongoose) { return { type: 'mongoose', entityPath: 'src/libs/dal/models', repositoryPath: 'src/libs/dal/repositories', modelPath: 'src/libs/dal/models' }; } throw new Error('No supported ORM detected. This package supports TypeORM and Mongoose.'); } /** * Generate repository for specified entity */ async generateRepository(entityName, options = {}) { const servicePath = options.servicePath || process.cwd(); const ormConfig = this.detectORM(servicePath); console.log(`🔍 Detected ORM: ${ormConfig.type}`); console.log(`📁 Service path: ${servicePath}`); // Check if entity/model exists this.validateEntityExists(entityName, ormConfig, servicePath); // Generate repository code const templateKey = ormConfig.type === 'typeorm' ? 'typeorm-repository' : 'mongoose-repository'; const template = this.templates.get(templateKey); if (!template) { throw new Error(`Template not found for ORM: ${ormConfig.type}`); } const templateData = { entityName, entityNameLower: entityName.toLowerCase(), entityNameKebab: this.toKebabCase(entityName), ormType: ormConfig.type, timestamp: new Date().toISOString(), // Add detected relations/properties from entity file ...this.analyzeEntity(entityName, ormConfig, servicePath) }; const content = template(templateData); // Ensure repository directory exists const repositoryDir = path.join(servicePath, ormConfig.repositoryPath); if (!fs.existsSync(repositoryDir)) { fs.mkdirSync(repositoryDir, { recursive: true }); } // Write repository file const fileName = `${entityName.toLowerCase()}.repository.ts`; const filePath = path.join(repositoryDir, fileName); if (fs.existsSync(filePath) && !options.force) { throw new Error(`Repository already exists: ${filePath}. Use --force to overwrite.`); } fs.writeFileSync(filePath, content); // Update index.ts exports this.updateRepositoryIndex(ormConfig, servicePath, entityName); console.log(`✅ Generated: ${path.relative(servicePath, filePath)}`); return { filePath, content }; } /** * Validate that the entity/model file exists */ validateEntityExists(entityName, ormConfig, servicePath) { const entityDir = path.join(servicePath, ormConfig.entityPath); if (!fs.existsSync(entityDir)) { throw new Error(`Entity directory not found: ${ormConfig.entityPath}`); } const possibleFiles = [ `${entityName.toLowerCase()}.entity.ts`, `${entityName.toLowerCase()}.model.ts`, `${this.toKebabCase(entityName)}.entity.ts`, `${this.toKebabCase(entityName)}.model.ts` ]; const existingFile = possibleFiles.find(file => fs.existsSync(path.join(entityDir, file))); if (!existingFile) { throw new Error(`Entity file not found. Looking for: ${possibleFiles.join(', ')} in ${ormConfig.entityPath}`); } console.log(`📄 Found entity: ${existingFile}`); } /** * Analyze entity file to extract relations/properties */ analyzeEntity(entityName, ormConfig, servicePath) { // This is a simplified version - could be enhanced with proper AST parsing const entityDir = path.join(servicePath, ormConfig.entityPath); const possibleFiles = [ `${entityName.toLowerCase()}.entity.ts`, `${entityName.toLowerCase()}.model.ts`, `${this.toKebabCase(entityName)}.entity.ts`, `${this.toKebabCase(entityName)}.model.ts` ]; const existingFile = possibleFiles.find(file => fs.existsSync(path.join(entityDir, file))); if (!existingFile) return {}; const content = fs.readFileSync(path.join(entityDir, existingFile), 'utf8'); // Extract basic relations (could be more sophisticated) const relations = this.extractRelations(content, ormConfig.type); const properties = this.extractProperties(content, ormConfig.type); return { relations, properties }; } /** * Extract relations from entity file */ extractRelations(content, ormType) { const relations = []; if (ormType === 'typeorm') { // Look for @OneToMany, @ManyToOne, @OneToOne, @ManyToMany const relationRegex = /@(?:OneToMany|ManyToOne|OneToOne|ManyToMany)[\s\S]*?(\w+):/g; let match; while ((match = relationRegex.exec(content)) !== null) { relations.push(match[1]); } } else { // Look for ref: properties in Mongoose schemas const refRegex = /(\w+):\s*\{[^}]*ref:\s*['"`](\w+)['"`]/g; let match; while ((match = refRegex.exec(content)) !== null) { relations.push(match[1]); } } return relations; } /** * Extract basic properties from entity file */ extractProperties(content, ormType) { const properties = []; // Simple property extraction - could be enhanced const propertyRegex = /^\s*(\w+):\s*\w+/gm; let match; while ((match = propertyRegex.exec(content)) !== null) { if (!['constructor', 'function', 'class'].includes(match[1])) { properties.push(match[1]); } } return properties.filter(prop => !['id', 'createdAt', 'updatedAt'].includes(prop)); } /** * Update repository index.ts file */ updateRepositoryIndex(ormConfig, servicePath, entityName) { const indexPath = path.join(servicePath, ormConfig.repositoryPath, 'index.ts'); const exportLine = `export { ${entityName}Repository } from './${entityName.toLowerCase()}.repository';`; let content = ''; if (fs.existsSync(indexPath)) { content = fs.readFileSync(indexPath, 'utf8'); } // Add export if not already present if (!content.includes(exportLine)) { content += content ? '\n' + exportLine : exportLine; fs.writeFileSync(indexPath, content); console.log(`✅ Updated: ${path.relative(servicePath, indexPath)}`); } } /** * Load Handlebars templates */ loadTemplates() { const templatesDir = path.join(__dirname, '../templates'); // TypeORM template const typeormTemplatePath = path.join(templatesDir, 'typeorm-repository.hbs'); if (fs.existsSync(typeormTemplatePath)) { const typeormTemplate = fs.readFileSync(typeormTemplatePath, 'utf8'); this.templates.set('typeorm-repository', Handlebars.compile(typeormTemplate)); } // Mongoose template const mongooseTemplatePath = path.join(templatesDir, 'mongoose-repository.hbs'); if (fs.existsSync(mongooseTemplatePath)) { const mongooseTemplate = fs.readFileSync(mongooseTemplatePath, 'utf8'); this.templates.set('mongoose-repository', Handlebars.compile(mongooseTemplate)); } } /** * Register Handlebars helpers */ registerHelpers() { Handlebars.registerHelper('toLowerCase', (str) => str.toLowerCase()); Handlebars.registerHelper('toUpperCase', (str) => str.toUpperCase()); Handlebars.registerHelper('toCamelCase', (str) => str.charAt(0).toLowerCase() + str.slice(1)); Handlebars.registerHelper('toPascalCase', (str) => str.charAt(0).toUpperCase() + str.slice(1)); Handlebars.registerHelper('toKebabCase', (str) => this.toKebabCase(str)); } /** * Convert string to kebab-case */ toKebabCase(str) { return str .replace(/([a-z])([A-Z])/g, '$1-$2') .toLowerCase(); } } exports.RepositoryGenerator = RepositoryGenerator;