artmapper
Version:
Spring Boot clone for Node.js with TypeScript/JavaScript - JPA-like ORM, Lombok decorators, dependency injection, and MySQL support
288 lines • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaGenerator = void 0;
require("reflect-metadata");
const entity_1 = require("../decorators/entity");
class SchemaGenerator {
constructor(pool) {
this.pool = pool;
}
/**
* Generate and execute CREATE TABLE statements for all entities
*/
async generateSchema(entities) {
for (const entityClass of entities) {
await this.createTable(entityClass);
}
console.log('✓ Database schema generated successfully');
}
/**
* Create a table for an entity
*/
async createTable(entityClass) {
const entityMetadata = Reflect.getMetadata(entity_1.ENTITY_METADATA_KEY, entityClass);
if (!entityMetadata) {
return; // Not an entity, skip
}
const tableMetadata = Reflect.getMetadata(entity_1.TABLE_METADATA_KEY, entityClass);
const columnMetadata = Reflect.getMetadata(entity_1.COLUMN_METADATA_KEY, entityClass) || {};
const idProperty = Reflect.getMetadata(entity_1.ID_METADATA_KEY, entityClass);
const generatedValue = Reflect.getMetadata(entity_1.GENERATED_VALUE_METADATA_KEY, entityClass);
const tableName = tableMetadata?.name || entityClass.name.toLowerCase();
// Check if table already exists
const tableExists = await this.tableExists(tableName);
if (tableExists) {
console.log(` Table '${tableName}' already exists, skipping...`);
return;
}
// Build CREATE TABLE statement
const columns = [];
let primaryKeyColumn = null;
Object.keys(columnMetadata).forEach(propertyKey => {
const colMeta = columnMetadata[propertyKey];
const colName = colMeta.name || propertyKey;
let colDef = `\`${colName}\` ${this.getSqlType(colMeta.type, colMeta.length, colMeta.precision, colMeta.scale, colMeta.enum, colMeta.unsigned)}`;
// Handle unsigned (for numeric types)
if (colMeta.unsigned && this.isNumericType(colMeta.type)) {
// Already handled in getSqlType, but keep for consistency
}
// Handle nullable
if (colMeta.nullable === false) {
colDef += ' NOT NULL';
}
// Handle unique
if (colMeta.unique) {
colDef += ' UNIQUE';
}
// Handle default
if (colMeta.default !== undefined) {
// Special handling for CURRENT_TIMESTAMP (should not be quoted)
if (colMeta.default === 'CURRENT_TIMESTAMP' || colMeta.default === 'CURRENT_TIMESTAMP()') {
colDef += ' DEFAULT CURRENT_TIMESTAMP';
}
else if (typeof colMeta.default === 'string') {
colDef += ` DEFAULT '${colMeta.default}'`;
}
else {
colDef += ` DEFAULT ${colMeta.default}`;
}
}
// Handle AUTO_INCREMENT for ID
if (propertyKey === idProperty && generatedValue?.strategy === 'AUTO') {
colDef += ' AUTO_INCREMENT';
primaryKeyColumn = colName;
}
// Handle ON UPDATE for TIMESTAMP columns
if (colMeta.onUpdate) {
if (colMeta.onUpdate === 'CURRENT_TIMESTAMP' || colMeta.onUpdate === 'CURRENT_TIMESTAMP()') {
colDef += ' ON UPDATE CURRENT_TIMESTAMP';
}
else {
colDef += ` ON UPDATE ${colMeta.onUpdate}`;
}
}
columns.push(colDef);
});
// Add primary key constraint
if (primaryKeyColumn) {
columns.push(`PRIMARY KEY (\`${primaryKeyColumn}\`)`);
}
const sql = `CREATE TABLE IF NOT EXISTS \`${tableName}\` (\n ${columns.join(',\n ')}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`;
try {
await this.pool.execute(sql);
console.log(` ✓ Created table '${tableName}'`);
}
catch (error) {
console.error(` ✗ Failed to create table '${tableName}':`, error.message);
throw error;
}
}
/**
* Check if a table exists
*/
async tableExists(tableName) {
try {
const [rows] = await this.pool.execute(`SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?`, [tableName]);
return rows[0].count > 0;
}
catch {
return false;
}
}
/**
* Convert TypeScript type to SQL type
*/
getSqlType(type, length, precision, scale, enumValues, unsigned) {
if (!type) {
return 'VARCHAR(255)';
}
const upperType = type.toUpperCase();
// Handle ENUM type
if (upperType === 'ENUM' && enumValues && enumValues.length > 0) {
const enumStr = enumValues.map(val => `'${this.escapeString(val)}'`).join(',');
return `ENUM(${enumStr})`;
}
switch (upperType) {
// Integer Types
case 'INT':
case 'INTEGER':
return unsigned ? 'INT UNSIGNED' : 'INT';
case 'BIGINT':
return unsigned ? 'BIGINT UNSIGNED' : 'BIGINT';
case 'SMALLINT':
return unsigned ? 'SMALLINT UNSIGNED' : 'SMALLINT';
case 'TINYINT':
return unsigned ? 'TINYINT UNSIGNED' : 'TINYINT';
case 'MEDIUMINT':
return unsigned ? 'MEDIUMINT UNSIGNED' : 'MEDIUMINT';
// Floating Point Types
case 'FLOAT':
if (precision !== undefined) {
return scale !== undefined
? `FLOAT(${precision},${scale})`
: `FLOAT(${precision})`;
}
return 'FLOAT';
case 'DOUBLE':
case 'REAL':
if (precision !== undefined) {
return scale !== undefined
? `DOUBLE(${precision},${scale})`
: `DOUBLE(${precision})`;
}
return 'DOUBLE';
case 'DECIMAL':
case 'NUMERIC':
case 'FIXED':
if (precision && scale) {
return `DECIMAL(${precision},${scale})`;
}
else if (precision) {
return `DECIMAL(${precision})`;
}
return 'DECIMAL(10,2)';
// String Types
case 'VARCHAR':
return length ? `VARCHAR(${length})` : 'VARCHAR(255)';
case 'CHAR':
return length ? `CHAR(${length})` : 'CHAR(1)';
case 'BINARY':
return length ? `BINARY(${length})` : 'BINARY(1)';
case 'VARBINARY':
return length ? `VARBINARY(${length})` : 'VARBINARY(255)';
case 'TEXT':
return 'TEXT';
case 'LONGTEXT':
return 'LONGTEXT';
case 'MEDIUMTEXT':
return 'MEDIUMTEXT';
case 'TINYTEXT':
return 'TINYTEXT';
// BLOB Types
case 'BLOB':
return 'BLOB';
case 'LONGBLOB':
return 'LONGBLOB';
case 'MEDIUMBLOB':
return 'MEDIUMBLOB';
case 'TINYBLOB':
return 'TINYBLOB';
// Date and Time Types
case 'DATE':
return 'DATE';
case 'TIME':
if (length !== undefined) {
return `TIME(${length})`;
}
return 'TIME';
case 'DATETIME':
if (length !== undefined) {
return `DATETIME(${length})`;
}
return 'DATETIME';
case 'TIMESTAMP':
if (length !== undefined) {
return `TIMESTAMP(${length})`;
}
return 'TIMESTAMP';
case 'YEAR':
return length ? `YEAR(${length})` : 'YEAR';
// Boolean Type (MySQL uses TINYINT(1))
case 'BOOLEAN':
case 'BOOL':
return 'TINYINT(1)';
// JSON Type
case 'JSON':
return 'JSON';
// Bit Type
case 'BIT':
return length ? `BIT(${length})` : 'BIT(1)';
// Geometry Types
case 'GEOMETRY':
return 'GEOMETRY';
case 'POINT':
return 'POINT';
case 'LINESTRING':
return 'LINESTRING';
case 'POLYGON':
return 'POLYGON';
case 'MULTIPOINT':
return 'MULTIPOINT';
case 'MULTILINESTRING':
return 'MULTILINESTRING';
case 'MULTIPOLYGON':
return 'MULTIPOLYGON';
case 'GEOMETRYCOLLECTION':
return 'GEOMETRYCOLLECTION';
// Set Type
case 'SET':
if (enumValues && enumValues.length > 0) {
const setStr = enumValues.map(val => `'${this.escapeString(val)}'`).join(',');
return `SET(${setStr})`;
}
return 'SET';
default:
return length ? `VARCHAR(${length})` : 'VARCHAR(255)';
}
}
/**
* Check if a type is numeric
*/
isNumericType(type) {
if (!type)
return false;
const upperType = type.toUpperCase();
return [
'INT', 'INTEGER', 'BIGINT', 'SMALLINT', 'TINYINT', 'MEDIUMINT',
'FLOAT', 'DOUBLE', 'REAL', 'DECIMAL', 'NUMERIC', 'FIXED', 'BIT'
].includes(upperType);
}
/**
* Escape string for SQL
*/
escapeString(str) {
return str.replace(/'/g, "''").replace(/\\/g, '\\\\');
}
/**
* Drop all tables for entities (use with caution!)
*/
async dropSchema(entities) {
for (const entityClass of entities) {
const entityMetadata = Reflect.getMetadata(entity_1.ENTITY_METADATA_KEY, entityClass);
if (!entityMetadata) {
continue;
}
const tableMetadata = Reflect.getMetadata(entity_1.TABLE_METADATA_KEY, entityClass);
const tableName = tableMetadata?.name || entityClass.name.toLowerCase();
try {
await this.pool.execute(`DROP TABLE IF EXISTS \`${tableName}\``);
console.log(` ✓ Dropped table '${tableName}'`);
}
catch (error) {
console.error(` ✗ Failed to drop table '${tableName}':`, error.message);
}
}
}
}
exports.SchemaGenerator = SchemaGenerator;
//# sourceMappingURL=SchemaGenerator.js.map