UNPKG

petcarescript

Version:

PetCareScript - A modern, expressive programming language designed for humans

375 lines (313 loc) 10.7 kB
/** * PetCareScript Database Library * SQLite database functionality with optional sqlite3 dependency */ let sqlite3; try { sqlite3 = require('sqlite3').verbose(); } catch (error) { // sqlite3 is optional dependency sqlite3 = null; } const path = require('path'); class Database { constructor(filename = ':memory:') { if (!sqlite3) { throw new Error('sqlite3 is not installed. Install it with: npm install sqlite3'); } this.filename = filename; this.db = null; this.isConnected = false; } async connect() { if (!sqlite3) { throw new Error('sqlite3 is not installed. Install it with: npm install sqlite3'); } return new Promise((resolve, reject) => { this.db = new sqlite3.Database(this.filename, (error) => { if (error) { reject(error); } else { this.isConnected = true; console.log(`📊 Connected to SQLite database: ${this.filename}`); resolve(); } }); }); } async close() { return new Promise((resolve, reject) => { if (this.db) { this.db.close((error) => { if (error) { reject(error); } else { this.isConnected = false; console.log('📊 Database connection closed'); resolve(); } }); } else { resolve(); } }); } async query(sql, params = []) { if (!sqlite3) { throw new Error('sqlite3 is not installed. Install it with: npm install sqlite3'); } return new Promise((resolve, reject) => { if (!this.isConnected) { reject(new Error('Database not connected')); return; } this.db.all(sql, params, (error, rows) => { if (error) { reject(error); } else { resolve(rows); } }); }); } async run(sql, params = []) { if (!sqlite3) { throw new Error('sqlite3 is not installed. Install it with: npm install sqlite3'); } return new Promise((resolve, reject) => { if (!this.isConnected) { reject(new Error('Database not connected')); return; } this.db.run(sql, params, function(error) { if (error) { reject(error); } else { resolve({ lastID: this.lastID, changes: this.changes }); } }); }); } async get(sql, params = []) { if (!sqlite3) { throw new Error('sqlite3 is not installed. Install it with: npm install sqlite3'); } return new Promise((resolve, reject) => { if (!this.isConnected) { reject(new Error('Database not connected')); return; } this.db.get(sql, params, (error, row) => { if (error) { reject(error); } else { resolve(row); } }); }); } async createTable(tableName, columns) { const columnDefs = Object.entries(columns).map(([name, type]) => { return `${name} ${type}`; }).join(', '); const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${columnDefs})`; return this.run(sql); } async insert(tableName, data) { const columns = Object.keys(data); const values = Object.values(data); const placeholders = values.map(() => '?').join(', '); const sql = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`; return this.run(sql, values); } async select(tableName, conditions = {}, options = {}) { let sql = `SELECT * FROM ${tableName}`; const params = []; if (Object.keys(conditions).length > 0) { const whereClause = Object.keys(conditions).map(key => { params.push(conditions[key]); return `${key} = ?`; }).join(' AND '); sql += ` WHERE ${whereClause}`; } if (options.orderBy) { sql += ` ORDER BY ${options.orderBy}`; if (options.order) { sql += ` ${options.order}`; } } if (options.limit) { sql += ` LIMIT ${options.limit}`; } if (options.offset) { sql += ` OFFSET ${options.offset}`; } return this.query(sql, params); } async update(tableName, data, conditions) { const setClause = Object.keys(data).map(key => `${key} = ?`).join(', '); const whereClause = Object.keys(conditions).map(key => `${key} = ?`).join(' AND '); const sql = `UPDATE ${tableName} SET ${setClause} WHERE ${whereClause}`; const params = [...Object.values(data), ...Object.values(conditions)]; return this.run(sql, params); } async delete(tableName, conditions) { const whereClause = Object.keys(conditions).map(key => `${key} = ?`).join(' AND '); const sql = `DELETE FROM ${tableName} WHERE ${whereClause}`; const params = Object.values(conditions); return this.run(sql, params); } async transaction(callback) { await this.run('BEGIN TRANSACTION'); try { await callback(this); await this.run('COMMIT'); } catch (error) { await this.run('ROLLBACK'); throw error; } } async migrate(migrations) { // Create migrations table await this.createTable('migrations', { id: 'INTEGER PRIMARY KEY AUTOINCREMENT', name: 'TEXT UNIQUE', executed_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }); for (const migration of migrations) { const existing = await this.get('SELECT * FROM migrations WHERE name = ?', [migration.name]); if (!existing) { console.log(`🔄 Running migration: ${migration.name}`); await migration.up(this); await this.insert('migrations', { name: migration.name }); console.log(`✅ Migration completed: ${migration.name}`); } } } async seed(seeds) { for (const seed of seeds) { console.log(`🌱 Running seed: ${seed.name}`); await seed.run(this); console.log(`✅ Seed completed: ${seed.name}`); } } } // Query builder class QueryBuilder { constructor(tableName) { this.tableName = tableName; this.selectFields = ['*']; this.whereConditions = []; this.joinClauses = []; this.orderByClause = null; this.limitClause = null; this.offsetClause = null; this.params = []; } select(fields) { if (Array.isArray(fields)) { this.selectFields = fields; } else { this.selectFields = [fields]; } return this; } where(field, operator, value) { if (arguments.length === 2) { // where(field, value) - assume equality value = operator; operator = '='; } this.whereConditions.push(`${field} ${operator} ?`); this.params.push(value); return this; } join(table, condition) { this.joinClauses.push(`JOIN ${table} ON ${condition}`); return this; } leftJoin(table, condition) { this.joinClauses.push(`LEFT JOIN ${table} ON ${condition}`); return this; } orderBy(field, direction = 'ASC') { this.orderByClause = `${field} ${direction}`; return this; } limit(count) { this.limitClause = count; return this; } offset(count) { this.offsetClause = count; return this; } build() { let sql = `SELECT ${this.selectFields.join(', ')} FROM ${this.tableName}`; if (this.joinClauses.length > 0) { sql += ` ${this.joinClauses.join(' ')}`; } if (this.whereConditions.length > 0) { sql += ` WHERE ${this.whereConditions.join(' AND ')}`; } if (this.orderByClause) { sql += ` ORDER BY ${this.orderByClause}`; } if (this.limitClause) { sql += ` LIMIT ${this.limitClause}`; } if (this.offsetClause) { sql += ` OFFSET ${this.offsetClause}`; } return { sql, params: this.params }; } async execute(db) { const { sql, params } = this.build(); return db.query(sql, params); } } const DatabaseLib = { connect: (filename) => { if (!sqlite3) { console.warn('⚠️ sqlite3 not installed. Database features will be limited.'); console.warn(' To enable database features, run: npm install sqlite3'); return { connect: () => Promise.reject(new Error('sqlite3 not installed')), createTable: () => Promise.reject(new Error('sqlite3 not installed')), insert: () => Promise.reject(new Error('sqlite3 not installed')), select: () => Promise.reject(new Error('sqlite3 not installed')), update: () => Promise.reject(new Error('sqlite3 not installed')), delete: () => Promise.reject(new Error('sqlite3 not installed')), close: () => Promise.resolve() }; } const db = new Database(filename); return db; }, query: (tableName) => new QueryBuilder(tableName), // Migration helpers createMigration: (name, up, down = null) => ({ name, up, down }), createSeed: (name, run) => ({ name, run }), // Data types for schema definition types: { INTEGER: 'INTEGER', TEXT: 'TEXT', REAL: 'REAL', BLOB: 'BLOB', PRIMARY_KEY: 'INTEGER PRIMARY KEY AUTOINCREMENT', TIMESTAMP: 'DATETIME DEFAULT CURRENT_TIMESTAMP', BOOLEAN: 'INTEGER', // SQLite doesn't have native boolean JSON: 'TEXT' // Store JSON as text } }; module.exports = DatabaseLib;