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
JavaScript
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 ? ` ` : '';
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;
}
}