UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

769 lines 23.4 kB
/** * @fileoverview OrdoJS Database Manager - Comprehensive database integration */ /** * Default database configuration */ const DEFAULT_DATABASE_CONFIG = { type: 'postgresql', host: 'localhost', port: 5432, database: 'ordojs', username: 'postgres', password: '', poolSize: 10, connectionTimeout: 30000, queryTimeout: 10000, enableSSL: false, enablePooling: true, enableQueryLogging: false, enableMigrations: true, migrationDir: './migrations', enableCaching: true, cacheTTL: 3600, enableRealtime: false, websocketConfig: { url: 'ws://localhost:8080', reconnectInterval: 5000, maxReconnectAttempts: 5 } }; /** * Comprehensive database manager for OrdoJS applications */ export class DatabaseManager { config; connection = null; pool = null; models = new Map(); cache = new Map(); subscriptions = new Map(); websocket = null; queryLog = []; migrations = new Map(); constructor(config = {}) { this.config = { ...DEFAULT_DATABASE_CONFIG, ...config }; this.initializeMigrations(); } /** * Connect to database */ async connect() { try { const startTime = Date.now(); if (this.config.enablePooling) { await this.createConnectionPool(); } else { await this.createSingleConnection(); } const connectionDuration = Date.now() - startTime; // Initialize real-time features if enabled if (this.config.enableRealtime) { await this.initializeRealtime(); } return { connected: true, poolStatus: this.getPoolStatus(), lastConnected: new Date(), connectionDuration }; } catch (error) { return { connected: false, error: error instanceof Error ? error.message : String(error), poolStatus: { total: 0, active: 0, idle: 0, waiting: 0 } }; } } /** * Disconnect from database */ async disconnect() { try { if (this.pool) { await this.pool.end(); this.pool = null; } if (this.connection) { await this.connection.close(); this.connection = null; } if (this.websocket) { this.websocket.close(); this.websocket = null; } } catch (error) { console.error('Error disconnecting from database:', error); } } /** * Execute query */ async query(sql, params = []) { const startTime = Date.now(); try { const connection = this.pool || this.connection; if (!connection) { throw new Error('Database not connected'); } const result = await this.executeQuery(connection, sql, params); const executionTime = Date.now() - startTime; // Log query if enabled if (this.config.enableQueryLogging) { this.logQuery(sql, params, executionTime); } return { success: true, data: result.data, affectedRows: result.affectedRows, insertId: result.insertId, executionTime, metadata: this.extractQueryMetadata(sql, result) }; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, error: error instanceof Error ? error.message : String(error), executionTime }; } } /** * Execute transaction */ async transaction(callback) { const startTime = Date.now(); try { const connection = this.pool || this.connection; if (!connection) { throw new Error('Database not connected'); } const transactionManager = new DatabaseManager(this.config); transactionManager.connection = await this.beginTransaction(connection); const result = await callback(transactionManager); await this.commitTransaction(transactionManager.connection); const executionTime = Date.now() - startTime; return { success: true, data: result, executionTime }; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, error: error instanceof Error ? error.message : String(error), executionTime }; } } /** * Define model */ defineModel(definition) { this.models.set(definition.name, definition); } /** * Create model instance */ createModel(modelName) { const definition = this.models.get(modelName); if (!definition) { throw new Error(`Model '${modelName}' not found`); } return new Model(this, definition); } /** * Run migrations */ async runMigrations() { const startTime = Date.now(); const applied = []; const rolledBack = []; try { // Create migrations table if it doesn't exist await this.createMigrationsTable(); // Get applied migrations const appliedMigrations = await this.getAppliedMigrations(); // Run pending migrations for (const [name, migration] of this.migrations) { if (!appliedMigrations.includes(name)) { const result = await this.executeMigration(name, migration.up); if (result.success) { await this.recordMigration(name); applied.push(name); } else { throw new Error(`Migration '${name}' failed: ${result.error}`); } } } const executionTime = Date.now() - startTime; return { success: true, name: 'migrations', applied, rolledBack, executionTime }; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, name: 'migrations', applied, rolledBack, error: error instanceof Error ? error.message : String(error), executionTime }; } } /** * Rollback migrations */ async rollbackMigrations(count = 1) { const startTime = Date.now(); const applied = []; const rolledBack = []; try { const appliedMigrations = await this.getAppliedMigrations(); const migrationsToRollback = appliedMigrations.slice(-count); for (const name of migrationsToRollback.reverse()) { const migration = this.migrations.get(name); if (migration) { const result = await this.executeMigration(name, migration.down); if (result.success) { await this.removeMigration(name); rolledBack.push(name); } else { throw new Error(`Rollback of migration '${name}' failed: ${result.error}`); } } } const executionTime = Date.now() - startTime; return { success: true, name: 'rollback', applied, rolledBack, executionTime }; } catch (error) { const executionTime = Date.now() - startTime; return { success: false, name: 'rollback', applied, rolledBack, error: error instanceof Error ? error.message : String(error), executionTime }; } } /** * Cache data */ async cacheSet(key, data, ttl = this.config.cacheTTL) { if (!this.config.enableCaching) { return; } const expiresAt = new Date(Date.now() + ttl * 1000); this.cache.set(key, { data, expiresAt }); } /** * Get cached data */ async cacheGet(key) { if (!this.config.enableCaching) { return { hit: false, key }; } const cached = this.cache.get(key); if (!cached) { return { hit: false, key }; } if (new Date() > cached.expiresAt) { this.cache.delete(key); return { hit: false, key }; } const ttl = Math.ceil((cached.expiresAt.getTime() - Date.now()) / 1000); return { hit: true, data: cached.data, key, ttl }; } /** * Delete cached data */ async cacheDelete(key) { this.cache.delete(key); } /** * Clear all cached data */ async cacheClear() { this.cache.clear(); } /** * Subscribe to real-time updates */ async subscribe(channel, event, callback) { if (!this.config.enableRealtime) { throw new Error('Real-time features are not enabled'); } const subscriptionId = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const subscription = { id: subscriptionId, channel, event, callback, active: true, createdAt: new Date() }; this.subscriptions.set(subscriptionId, subscription); // Send subscription message to WebSocket if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { this.websocket.send(JSON.stringify({ type: 'subscribe', channel, event, subscriptionId })); } return subscriptionId; } /** * Unsubscribe from real-time updates */ async unsubscribe(subscriptionId) { const subscription = this.subscriptions.get(subscriptionId); if (subscription) { subscription.active = false; this.subscriptions.delete(subscriptionId); // Send unsubscribe message to WebSocket if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { this.websocket.send(JSON.stringify({ type: 'unsubscribe', subscriptionId })); } } } /** * Publish real-time update */ async publish(channel, event, data) { if (!this.config.enableRealtime) { throw new Error('Real-time features are not enabled'); } // Send publish message to WebSocket if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { this.websocket.send(JSON.stringify({ type: 'publish', channel, event, data })); } } /** * Get connection status */ getConnectionStatus() { return { connected: !!(this.pool || this.connection), poolStatus: this.getPoolStatus(), lastConnected: new Date() }; } /** * Get query log */ getQueryLog() { return [...this.queryLog]; } /** * Clear query log */ clearQueryLog() { this.queryLog = []; } /** * Get all models */ getModels() { return Array.from(this.models.values()); } /** * Get all subscriptions */ getSubscriptions() { return Array.from(this.subscriptions.values()).filter(sub => sub.active); } /** * Create connection pool */ async createConnectionPool() { // In a real implementation, you'd use a proper database driver // This is a simplified version for demonstration this.pool = { query: async (sql, params) => { // Simulate database query return { rows: [], rowCount: 0 }; }, end: async () => { // Simulate pool cleanup } }; } /** * Create single connection */ async createSingleConnection() { // In a real implementation, you'd use a proper database driver // This is a simplified version for demonstration this.connection = { query: async (sql, params) => { // Simulate database query return { rows: [], rowCount: 0 }; }, close: async () => { // Simulate connection cleanup } }; } /** * Execute query */ async executeQuery(connection, sql, params) { // In a real implementation, you'd execute the actual query // This is a simplified version for demonstration const result = await connection.query(sql, params); return { data: result.rows, affectedRows: result.rowCount, insertId: result.insertId }; } /** * Begin transaction */ async beginTransaction(connection) { // In a real implementation, you'd begin a transaction // This is a simplified version for demonstration return connection; } /** * Commit transaction */ async commitTransaction(connection) { // In a real implementation, you'd commit the transaction // This is a simplified version for demonstration } /** * Get pool status */ getPoolStatus() { if (!this.pool) { return { total: 0, active: 0, idle: 0, waiting: 0 }; } // In a real implementation, you'd get actual pool statistics return { total: 10, active: 2, idle: 8, waiting: 0 }; } /** * Log query */ logQuery(sql, params, executionTime) { this.queryLog.push({ query: sql, params, executionTime, timestamp: new Date() }); // Keep only last 1000 queries if (this.queryLog.length > 1000) { this.queryLog = this.queryLog.slice(-1000); } } /** * Extract query metadata */ extractQueryMetadata(sql, result) { const upperSql = sql.trim().toUpperCase(); let type = 'SELECT'; let table; if (upperSql.startsWith('INSERT')) type = 'INSERT'; else if (upperSql.startsWith('UPDATE')) type = 'UPDATE'; else if (upperSql.startsWith('DELETE')) type = 'DELETE'; else if (upperSql.startsWith('CREATE')) type = 'CREATE'; else if (upperSql.startsWith('DROP')) type = 'DROP'; else if (upperSql.startsWith('ALTER')) type = 'ALTER'; // Extract table name (simplified) const tableMatch = sql.match(/(?:FROM|INTO|UPDATE)\s+(\w+)/i); if (tableMatch) { table = tableMatch[1]; } return { type, table, columnCount: result.data?.[0] ? Object.keys(result.data[0]).length : 0, rowCount: result.data?.length || 0 }; } /** * Initialize migrations */ initializeMigrations() { // Add some example migrations this.migrations.set('001_create_users_table', { up: ` CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `, down: 'DROP TABLE users;' }); this.migrations.set('002_create_posts_table', { up: ` CREATE TABLE posts ( id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, content TEXT, user_id INTEGER REFERENCES users(id), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `, down: 'DROP TABLE posts;' }); } /** * Create migrations table */ async createMigrationsTable() { const sql = ` CREATE TABLE IF NOT EXISTS migrations ( id SERIAL PRIMARY KEY, name VARCHAR(255) UNIQUE NOT NULL, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `; await this.query(sql); } /** * Get applied migrations */ async getAppliedMigrations() { const result = await this.query('SELECT name FROM migrations ORDER BY id'); return result.success ? result.data?.map((row) => row.name) || [] : []; } /** * Execute migration */ async executeMigration(name, sql) { return await this.query(sql); } /** * Record migration */ async recordMigration(name) { await this.query('INSERT INTO migrations (name) VALUES ($1)', [name]); } /** * Remove migration */ async removeMigration(name) { await this.query('DELETE FROM migrations WHERE name = $1', [name]); } /** * Initialize real-time features */ async initializeRealtime() { if (!this.config.websocketConfig) { return; } this.websocket = new WebSocket(this.config.websocketConfig.url); this.websocket.onopen = () => { console.log('WebSocket connected for real-time features'); }; this.websocket.onmessage = (event) => { try { const message = JSON.parse(event.data); this.handleRealtimeMessage(message); } catch (error) { console.error('Error parsing WebSocket message:', error); } }; this.websocket.onerror = (error) => { console.error('WebSocket error:', error); }; this.websocket.onclose = () => { console.log('WebSocket disconnected'); // Attempt to reconnect setTimeout(() => this.initializeRealtime(), this.config.websocketConfig.reconnectInterval); }; } /** * Handle real-time message */ handleRealtimeMessage(message) { if (message.type === 'publish') { // Notify relevant subscriptions for (const subscription of this.subscriptions.values()) { if (subscription.active && subscription.channel === message.channel && subscription.event === message.event) { subscription.callback(message.data); } } } } } /** * Model class for ORM functionality */ export class Model { db; definition; constructor(db, definition) { this.db = db; this.definition = definition; } /** * Find all records */ async findAll(options = {}) { const sql = this.buildSelectQuery(options); return await this.db.query(sql, options.params || []); } /** * Find record by ID */ async findById(id, options = {}) { const sql = this.buildSelectQuery({ ...options, where: { id } }); const result = await this.db.query(sql, options.params || []); if (result.success && result.data && result.data.length > 0) { return { ...result, data: result.data[0] }; } return { ...result, data: undefined }; } /** * Find one record */ async findOne(options = {}) { const sql = this.buildSelectQuery({ ...options, limit: 1 }); const result = await this.db.query(sql, options.params || []); if (result.success && result.data && result.data.length > 0) { return { ...result, data: result.data[0] }; } return { ...result, data: undefined }; } /** * Create record */ async create(data) { const fields = Object.keys(data); const values = Object.values(data); const placeholders = fields.map((_, index) => `$${index + 1}`).join(', '); const sql = ` INSERT INTO ${this.definition.table} (${fields.join(', ')}) VALUES (${placeholders}) RETURNING * `; return await this.db.query(sql, values); } /** * Update record */ async update(id, data) { const fields = Object.keys(data); const values = Object.values(data); const setClause = fields.map((field, index) => `${field} = $${index + 1}`).join(', '); const sql = ` UPDATE ${this.definition.table} SET ${setClause} WHERE id = $${values.length + 1} RETURNING * `; return await this.db.query(sql, [...values, id]); } /** * Delete record */ async delete(id) { const sql = ` DELETE FROM ${this.definition.table} WHERE id = $1 `; return await this.db.query(sql, [id]); } /** * Count records */ async count(options = {}) { const sql = this.buildCountQuery(options); const result = await this.db.query(sql, options.params || []); if (result.success && result.data && result.data.length > 0) { return { ...result, data: parseInt(result.data[0].count) }; } return { ...result, data: 0 }; } /** * Build SELECT query */ buildSelectQuery(options) { let sql = `SELECT ${options.select || '*'} FROM ${this.definition.table}`; if (options.where) { const whereConditions = Object.entries(options.where) .map(([key, value]) => `${key} = $${options.params?.length || 0 + 1}`) .join(' AND '); sql += ` WHERE ${whereConditions}`; } if (options.orderBy) { sql += ` ORDER BY ${options.orderBy}`; } if (options.limit) { sql += ` LIMIT ${options.limit}`; } if (options.offset) { sql += ` OFFSET ${options.offset}`; } return sql; } /** * Build COUNT query */ buildCountQuery(options) { let sql = `SELECT COUNT(*) as count FROM ${this.definition.table}`; if (options.where) { const whereConditions = Object.entries(options.where) .map(([key, value]) => `${key} = $${options.params?.length || 0 + 1}`) .join(' AND '); sql += ` WHERE ${whereConditions}`; } return sql; } } //# sourceMappingURL=database-manager.js.map