@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
769 lines • 23.4 kB
JavaScript
/**
* @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