UNPKG

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
"use strict"; 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