@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
JavaScript
;
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;