UNPKG

modurize

Version:

Intelligent CLI tool to scaffold dynamic, context-aware modules for Node.js apps with smart CRUD generation and database integration

424 lines (356 loc) 12.9 kB
import { CRUDGenerator } from './crudGenerator.js'; export class ModelGenerator { constructor(moduleName, analysis) { this.moduleName = moduleName; this.analysis = analysis; this.capName = this.capitalize(moduleName); this.crudGenerator = new CRUDGenerator(moduleName, analysis); } capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } generateModel(useTypeScript = false) { const database = this.analysis.database; switch (database) { case 'mongodb': return this.generateMongoModel(useTypeScript); case 'postgresql': return this.generatePostgresModel(useTypeScript); case 'mysql': return this.generateMySQLModel(useTypeScript); case 'prisma': return this.generatePrismaModel(useTypeScript); default: return this.generateGenericModel(useTypeScript); } } generateMongoModel(useTypeScript) { const fields = this.crudGenerator.generateFields(); const fieldNames = Object.keys(fields); if (useTypeScript) { return `// ${this.moduleName}.model.ts import mongoose, { Document, Schema } from 'mongoose'; export interface I${this.capName} extends Document { ${this.generateTypeScriptInterface(fields)} } const ${this.moduleName}Schema = new Schema<I${this.capName}>({ ${this.generateMongoSchema(fields)} }, { timestamps: true, collection: '${this.moduleName}s' }); // Indexes ${this.generateMongoIndexes(fields)} // Virtuals ${this.moduleName}Schema.virtual('id').get(function() { return this._id.toHexString(); }); // Ensure virtual fields are serialized ${this.moduleName}Schema.set('toJSON', { virtuals: true, transform: function(doc, ret) { ret.id = ret._id; delete ret._id; delete ret.__v; return ret; } }); export const ${this.capName}Model = mongoose.model<I${this.capName}>('${this.capName}', ${this.moduleName}Schema); export default ${this.capName}Model;`; } else { return `// ${this.moduleName}.model.js import mongoose from 'mongoose'; const ${this.moduleName}Schema = new mongoose.Schema({ ${this.generateMongoSchema(fields)} }, { timestamps: true, collection: '${this.moduleName}s' }); // Indexes ${this.generateMongoIndexes(fields)} // Virtuals ${this.moduleName}Schema.virtual('id').get(function() { return this._id.toHexString(); }); // Ensure virtual fields are serialized ${this.moduleName}Schema.set('toJSON', { virtuals: true, transform: function(doc, ret) { ret.id = ret._id; delete ret._id; delete ret.__v; return ret; } }); export const ${this.capName}Model = mongoose.model('${this.capName}', ${this.moduleName}Schema); export default ${this.capName}Model;`; } } generatePostgresModel(useTypeScript) { const fields = this.crudGenerator.generateFields(); if (useTypeScript) { return `// ${this.moduleName}.model.ts import { DataTypes, Model, Optional } from 'sequelize'; import { sequelize } from '../config/database'; export interface ${this.capName}Attributes { ${this.generateTypeScriptInterface(fields)} } export interface ${this.capName}CreationAttributes extends Optional<${this.capName}Attributes, 'id' | 'createdAt' | 'updatedAt'> {} export class ${this.capName}Model extends Model<${this.capName}Attributes, ${this.capName}CreationAttributes> implements ${this.capName}Attributes { ${this.generateSequelizeAttributes(fields)} } ${this.capName}Model.init({ ${this.generateSequelizeSchema(fields)} }, { sequelize, tableName: '${this.moduleName}s', modelName: '${this.capName}', timestamps: true, underscored: true }); export default ${this.capName}Model;`; } else { return `// ${this.moduleName}.model.js import { DataTypes } from 'sequelize'; import { sequelize } from '../config/database'; const ${this.capName}Model = sequelize.define('${this.capName}', { ${this.generateSequelizeSchema(fields)} }, { tableName: '${this.moduleName}s', modelName: '${this.capName}', timestamps: true, underscored: true }); export default ${this.capName}Model;`; } } generateMySQLModel(useTypeScript) { // MySQL models are similar to PostgreSQL with Sequelize return this.generatePostgresModel(useTypeScript); } generatePrismaModel(useTypeScript) { const fields = this.crudGenerator.generateFields(); return `// ${this.moduleName}.model.ts // This is a Prisma schema definition // Add this to your schema.prisma file model ${this.capName} { ${this.generatePrismaSchema(fields)} } // Prisma Client usage example: // import { PrismaClient } from '@prisma/client'; // const prisma = new PrismaClient(); // // export const ${this.capName}Model = { // findMany: () => prisma.${this.moduleName}.findMany(), // findUnique: (id) => prisma.${this.moduleName}.findUnique({ where: { id } }), // create: (data) => prisma.${this.moduleName}.create({ data }), // update: (id, data) => prisma.${this.moduleName}.update({ where: { id }, data }), // delete: (id) => prisma.${this.moduleName}.delete({ where: { id } }) // };`; } generateGenericModel(useTypeScript) { const fields = this.crudGenerator.generateFields(); if (useTypeScript) { return `// ${this.moduleName}.model.ts // Generic model interface - implement based on your database export interface ${this.capName} { ${this.generateTypeScriptInterface(fields)} } export interface Create${this.capName}Input { ${this.generateCreateInterface(fields)} } export interface Update${this.capName}Input { ${this.generateUpdateInterface(fields)} } // Database-specific implementations: // For MongoDB with Mongoose: // import mongoose, { Schema } from 'mongoose'; // const ${this.moduleName}Schema = new Schema({ ... }); // export const ${this.capName}Model = mongoose.model('${this.capName}', ${this.moduleName}Schema); // For PostgreSQL/MySQL with Sequelize: // import { DataTypes, Model } from 'sequelize'; // export class ${this.capName}Model extends Model { ... } // ${this.capName}Model.init({ ... }, { sequelize, tableName: '${this.moduleName}s' }); // For Prisma: // Add to schema.prisma: model ${this.capName} { ... } // import { PrismaClient } from '@prisma/client'; // const prisma = new PrismaClient(); export const ${this.moduleName}Schema = { ${this.generateGenericSchema(fields)} };`; } else { return `// ${this.moduleName}.model.js // Generic model - implement based on your database // For MongoDB with Mongoose: // const mongoose = require('mongoose'); // const ${this.moduleName}Schema = new mongoose.Schema({ ... }); // module.exports = mongoose.model('${this.capName}', ${this.moduleName}Schema); // For PostgreSQL/MySQL with Sequelize: // const { DataTypes } = require('sequelize'); // const ${this.capName}Model = sequelize.define('${this.capName}', { ... }); // For Prisma: // Add to schema.prisma: model ${this.capName} { ... } const ${this.moduleName}Schema = { ${this.generateGenericSchema(fields)} }; export default ${this.moduleName}Schema;`; } } generateTypeScriptInterface(fields) { return Object.entries(fields) .map(([fieldName, field]) => { const type = this.getTypeScriptType(field.type); const optional = field.required ? '' : '?'; return ` ${fieldName}${optional}: ${type};`; }) .join('\n'); } generateCreateInterface(fields) { return Object.entries(fields) .filter(([fieldName, field]) => !field.primary && fieldName !== 'createdAt' && fieldName !== 'updatedAt') .map(([fieldName, field]) => { const type = this.getTypeScriptType(field.type); const optional = field.required ? '' : '?'; return ` ${fieldName}${optional}: ${type};`; }) .join('\n'); } generateUpdateInterface(fields) { return Object.entries(fields) .filter(([fieldName, field]) => !field.primary && fieldName !== 'createdAt' && fieldName !== 'updatedAt') .map(([fieldName, field]) => { const type = this.getTypeScriptType(field.type); return ` ${fieldName}?: ${type};`; }) .join('\n'); } generateMongoSchema(fields) { return Object.entries(fields) .map(([fieldName, field]) => { const mongoType = this.getMongoType(field.type); const required = field.required ? ', required: true' : ''; const unique = field.unique ? ', unique: true' : ''; const defaultVal = field.default ? `, default: ${this.getMongoDefault(field.default)}` : ''; return ` ${fieldName}: { type: ${mongoType}${required}${unique}${defaultVal} }`; }) .join(',\n'); } generateMongoIndexes(fields) { const indexes = []; // Add unique indexes Object.entries(fields) .filter(([_, field]) => field.unique) .forEach(([fieldName, _]) => { indexes.push(`${this.moduleName}Schema.index({ ${fieldName}: 1 }, { unique: true });`); }); // Add common indexes if (fields.email) { indexes.push(`${this.moduleName}Schema.index({ email: 1 });`); } if (fields.name) { indexes.push(`${this.moduleName}Schema.index({ name: 1 });`); } if (fields.isActive) { indexes.push(`${this.moduleName}Schema.index({ isActive: 1 });`); } return indexes.join('\n'); } generateSequelizeAttributes(fields) { return Object.entries(fields) .map(([fieldName, field]) => { const type = this.getSequelizeType(field.type); return ` ${fieldName}!: ${type};`; }) .join('\n'); } generateSequelizeSchema(fields) { return Object.entries(fields) .map(([fieldName, field]) => { const sequelizeType = this.getSequelizeType(field.type); const required = field.required ? ', allowNull: false' : ''; const unique = field.unique ? ', unique: true' : ''; const defaultVal = field.default ? `, defaultValue: ${this.getSequelizeDefault(field.default)}` : ''; return ` ${fieldName}: { type: ${sequelizeType}${required}${unique}${defaultVal} }`; }) .join(',\n'); } generatePrismaSchema(fields) { return Object.entries(fields) .map(([fieldName, field]) => { const prismaType = this.getPrismaType(field.type); const required = field.required ? '' : '?'; const unique = field.unique ? ' @unique' : ''; const defaultVal = field.default ? ` @default(${this.getPrismaDefault(field.default)})` : ''; return ` ${fieldName}${required} ${prismaType}${unique}${defaultVal}`; }) .join('\n'); } generateGenericSchema(fields) { return Object.entries(fields) .map(([fieldName, field]) => { return ` ${fieldName}: { type: '${field.type}', required: ${field.required}${field.unique ? ', unique: true' : ''} }`; }) .join(',\n'); } getTypeScriptType(fieldType) { const typeMap = { 'string': 'string', 'number': 'number', 'boolean': 'boolean', 'date': 'Date', 'array': 'any[]', 'object': 'any' }; return typeMap[fieldType] || 'any'; } getMongoType(fieldType) { const typeMap = { 'string': 'String', 'number': 'Number', 'boolean': 'Boolean', 'date': 'Date', 'array': 'Array', 'object': 'Object' }; return typeMap[fieldType] || 'String'; } getSequelizeType(fieldType) { const typeMap = { 'string': 'string', 'number': 'number', 'boolean': 'boolean', 'date': 'Date', 'array': 'DataTypes.JSON', 'object': 'DataTypes.JSON' }; return typeMap[fieldType] || 'string'; } getPrismaType(fieldType) { const typeMap = { 'string': 'String', 'number': 'Int', 'boolean': 'Boolean', 'date': 'DateTime', 'array': 'Json', 'object': 'Json' }; return typeMap[fieldType] || 'String'; } getMongoDefault(defaultVal) { if (defaultVal === 'now') return 'Date.now'; if (typeof defaultVal === 'string') return `'${defaultVal}'`; return defaultVal; } getSequelizeDefault(defaultVal) { if (defaultVal === 'now') return 'DataTypes.NOW'; if (typeof defaultVal === 'string') return `'${defaultVal}'`; return defaultVal; } getPrismaDefault(defaultVal) { if (defaultVal === 'now') return 'now()'; if (typeof defaultVal === 'string') return `"${defaultVal}"`; return defaultVal; } }