UNPKG

@onurege3467/zerohelper

Version:

ZeroHelper is a versatile high-performance utility library and database framework for Node.js, fully written in TypeScript.

850 lines (848 loc) • 36.8 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const inquirer_1 = __importDefault(require("inquirer")); const ora_1 = __importDefault(require("ora")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const index_1 = require("../index"); const package_json_1 = require("../package.json"); const program = new commander_1.Command(); program .name('zero') .description(chalk_1.default.cyan('ZeroHelper CLI - Elite Database Management Tool')) .version(package_json_1.version); function loadConfig(configPath) { const absolutePath = path_1.default.resolve(process.cwd(), configPath); if (!fs_1.default.existsSync(absolutePath)) { throw new Error(`Configuration file not found: ${configPath}`); } try { const modulePath = require.resolve(absolutePath); delete require.cache[modulePath]; const config = require(absolutePath); if (!config.zeroConfig) { throw new Error('Configuration file must export "zeroConfig"'); } return config.zeroConfig; } catch (error) { if (error.code === 'MODULE_NOT_FOUND') { throw new Error(`Cannot load config. Make sure TypeScript is compiled: ${error.message}`); } throw error; } } async function getDatabase(configPath) { const config = loadConfig(configPath); const db = index_1.database.createDatabase(config); return db; } program.command('init') .description('Initialize ZeroHelper in your project (Interactive)') .action(async () => { console.log(chalk_1.default.bold.blue(`\nšŸš€ Welcome to ZeroHelper v${package_json_1.version} Setup\n`)); const answers = await inquirer_1.default.prompt([ { type: 'list', name: 'adapter', message: 'Select database adapter:', choices: [ { name: 'šŸ“„ JSON (Simple file-based)', value: 'json' }, { name: 'šŸ“¦ ZPack (High-performance binary)', value: 'zpack' }, { name: 'šŸ’¾ SQLite (Embedded SQL)', value: 'sqlite' }, { name: '🐬 MySQL (Network SQL)', value: 'mysql' }, { name: '🐘 PostgreSQL (Network SQL)', value: 'postgres' }, { name: 'šŸƒ MongoDB (Document NoSQL)', value: 'mongodb' }, { name: '⚔ Redis (In-memory cache)', value: 'redis' }, { name: 'šŸ“Š TOON (Native TOON DB)', value: 'toon' } ] }, { type: 'confirm', name: 'enableCache', message: 'Enable caching layer?', default: true } ]); const prompts = []; if (['json', 'zpack', 'sqlite', 'toon'].includes(answers.adapter)) { prompts.push({ type: 'input', name: 'filePath', message: 'Enter database file path:', default: (ans) => { const ext = ans.adapter === 'json' ? '.json' : ans.adapter === 'zpack' ? '.zpack' : ans.adapter === 'toon' ? '.toon' : '.db'; return `./data/storage${ext}`; }, validate: (input) => input.length > 0 || 'Path is required' }); } else { prompts.push({ type: 'input', name: 'host', message: 'Database host:', default: 'localhost', validate: (input) => input.length > 0 || 'Host is required' }, { type: 'number', name: 'port', message: 'Database port:', default: (ans) => { const ports = { mysql: 3306, postgres: 5432, mongodb: 27017, redis: 6379 }; return ports[ans.adapter] || 3306; } }, { type: 'input', name: 'username', message: 'Username:', default: 'root', when: (ans) => ans.adapter !== 'mongodb' }, { type: 'password', name: 'password', message: 'Password (leave empty if none):' }, { type: 'input', name: 'database', message: 'Database name:', default: 'zero_db', validate: (input) => input.length > 0 || 'Database name is required' }); } if (answers.enableCache) { prompts.push({ type: 'list', name: 'cacheType', message: 'Cache type:', choices: ['memory', 'redis'], default: 'memory' }); prompts.push({ type: 'number', name: 'cacheTtl', message: 'Cache TTL (seconds):', default: 300, when: (ans) => ans.cacheType === 'memory' }); } const extraAnswers = await inquirer_1.default.prompt(prompts); const finalAnswers = { ...answers, ...extraAnswers }; const spinner = (0, ora_1.default)('Creating configuration...').start(); try { const configObject = buildConfig(finalAnswers); const configTemplate = formatConfigTemplate(finalAnswers, configObject); fs_1.default.writeFileSync(path_1.default.join(process.cwd(), 'zero.config.ts'), configTemplate); spinner.succeed(chalk_1.default.green('āœ… zero.config.ts created successfully!')); console.log(chalk_1.default.gray('\nšŸ“ Usage example:')); console.log(chalk_1.default.yellow(`import { database } from '@onurege3467/zerohelper';`)); console.log(chalk_1.default.yellow(`import { zeroConfig } from './zero.config';`)); console.log(chalk_1.default.yellow(`const db = database.createDatabase(zeroConfig);`)); } catch (error) { spinner.fail(chalk_1.default.red('Configuration creation failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); function buildConfig(answers) { if (['json', 'zpack', 'sqlite', 'toon'].includes(answers.adapter)) { return JSON.stringify({ adapter: answers.adapter, config: { path: answers.filePath, ...(answers.enableCache && { cache: { type: answers.cacheType || 'memory', ...(answers.cacheType === 'memory' && answers.cacheTtl && { ttl: answers.cacheTtl * 1000 }) } }) } }, null, 2); } const config = { adapter: answers.adapter, config: { host: answers.host, port: answers.port, ...(answers.username && { username: answers.username }), ...(answers.password && { password: answers.password }), database: answers.database, ...(answers.adapter === 'mongodb' && { url: `mongodb://${answers.host}:${answers.port}/${answers.database}` }) } }; if (answers.enableCache && answers.cacheType) { config.config.cache = { type: answers.cacheType }; if (answers.cacheType === 'redis') { config.config.cache.host = answers.host; config.config.cache.port = 6379; } else if (answers.cacheTtl) { config.config.cache.ttl = answers.cacheTtl * 1000; } } return JSON.stringify(config, null, 2); } function formatConfigTemplate(answers, configObject) { return `/** * ZeroHelper Configuration * Generated on ${new Date().toLocaleDateString()} */ export const zeroConfig = ${configObject}; `; } program.command('db:test') .description('Test database connection and show basic stats') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .action(async (options) => { const spinner = (0, ora_1.default)('Testing database connection...').start(); try { const db = await getDatabase(options.config); spinner.succeed(chalk_1.default.green('āœ… Database connection successful')); console.log(chalk_1.default.bold('\nšŸ“Š Database Stats:')); console.log(chalk_1.default.cyan(' Adapter:'), chalk_1.default.white(db.constructor.name)); console.log(chalk_1.default.cyan(' Status:'), chalk_1.default.green('Connected')); try { const metrics = db.getMetrics(); console.log(chalk_1.default.cyan(' Operations:'), chalk_1.default.white(metrics.database.count)); console.log(chalk_1.default.cyan(' Avg Latency:'), chalk_1.default.white(`${metrics.database.averageDuration.toFixed(2)}ms`)); } catch (err) { console.log(chalk_1.default.yellow(' Note: Metrics not available for this adapter')); } await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Connection failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('db:stats') .description('Show detailed database performance metrics') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .action(async (options) => { try { const db = await getDatabase(options.config); const metrics = db.getMetrics(); console.log(chalk_1.default.bold('\nšŸ“Š Database Performance Dashboard')); console.log(chalk_1.default.gray('─'.repeat(50))); console.log(chalk_1.default.bold('\nšŸ”¹ Database Operations:')); if (metrics.database) { const count = metrics.database.count ?? 0; const totalDuration = metrics.database.totalDuration ?? 0; const avgLatency = metrics.database.averageDuration ?? 0; const minDuration = metrics.database.minDuration ?? 0; const maxDuration = metrics.database.maxDuration ?? 0; const formatNum = (n) => typeof n === 'number' ? n.toFixed(2) : n; console.log(` Total Operations: ${chalk_1.default.cyan(count)}`); console.log(` Total Duration: ${chalk_1.default.cyan(`${formatNum(totalDuration)} ms`)}`); console.log(` Avg Latency: ${chalk_1.default.green(`${formatNum(avgLatency)} ms`)}`); console.log(` Min Duration: ${chalk_1.default.yellow(`${formatNum(minDuration)} ms`)}`); console.log(` Max Duration: ${chalk_1.default.yellow(`${formatNum(maxDuration)} ms`)}`); } else { console.log(chalk_1.default.gray(' No metrics available for this adapter')); } if (metrics.cache) { console.log(chalk_1.default.bold('\nšŸ”¹ Cache Performance:')); console.log(` Total Requests: ${chalk_1.default.cyan((metrics.cache.hits || 0) + (metrics.cache.misses || 0))}`); console.log(` Cache Hits: ${chalk_1.default.green(metrics.cache.hits || 0)}`); console.log(` Cache Misses: ${chalk_1.default.red(metrics.cache.misses || 0)}`); const hits = metrics.cache.hits || 0; const misses = metrics.cache.misses || 0; const total = hits + misses; if (total > 0) { const ratio = (hits / total) * 100; console.log(` Hit Ratio: ${ratio.toFixed(1)}% ${ratio > 80 ? 'āœ…' : ratio > 50 ? 'āš ļø' : 'āŒ'}`); } } console.log(chalk_1.default.gray('\n' + '─'.repeat(50))); await db.close(); } catch (error) { console.error(chalk_1.default.red(`Error: ${error.message}`)); process.exit(1); } }); program.command('db:seed') .description('Seed table with mock data') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-t, --table <name>', 'Table name') .option('-n, --count <number>', 'Number of records', '10') .action(async (options) => { if (!options.table) { console.error(chalk_1.default.red('Error: --table option is required')); process.exit(1); } const spinner = (0, ora_1.default)(`Seeding ${options.table}...`).start(); try { const db = await getDatabase(options.config); const seeder = new index_1.database.DataSeeder(db); const schema = {}; const fieldTypes = ['string', 'number', 'email', 'boolean', 'date']; for (let i = 0; i < 3; i++) { const fieldType = fieldTypes[Math.floor(Math.random() * fieldTypes.length)]; schema[`field_${i + 1}`] = { type: fieldType }; } const count = await seeder.seed(options.table, parseInt(options.count), schema); spinner.succeed(chalk_1.default.green(`āœ… Seeded ${count} records into ${options.table}`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Seeding failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('migrate') .description('Run pending migrations') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations') .action(async (options) => { const spinner = (0, ora_1.default)('Loading migrations...').start(); try { const db = await getDatabase(options.config); const migration = new index_1.database.MigrationManager(db, { migrationsDir: options.migrationsDir }); const pending = await migration.getPendingMigrations(); if (pending.length === 0) { spinner.succeed(chalk_1.default.green('āœ… No pending migrations')); await db.close(); return; } spinner.text = `Running ${pending.length} migration(s)...`; for (const m of pending) { await migration.runMigration(m, 'up'); spinner.text = `āœ… ${m.name}`; } spinner.succeed(chalk_1.default.green(`āœ… ${pending.length} migration(s) executed successfully`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Migration failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('migration:rollback') .description('Rollback the last migration(s)') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations') .option('-s, --steps <number>', 'Number of migrations to rollback', '1') .action(async (options) => { const steps = parseInt(options.steps); const { confirm } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirm', message: chalk_1.default.yellow(`āš ļø Are you sure you want to rollback ${steps} migration(s)?`), default: false } ]); if (!confirm) { console.log(chalk_1.default.yellow('Rollback cancelled')); return; } const spinner = (0, ora_1.default)(`Rolling back ${steps} migration(s)...`).start(); try { const db = await getDatabase(options.config); const migration = new index_1.database.MigrationManager(db, { migrationsDir: options.migrationsDir }); await migration.rollback(steps); spinner.succeed(chalk_1.default.green(`āœ… Rolled back ${steps} migration(s)`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Rollback failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('migration:status') .description('Show migration status') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations') .action(async (options) => { try { const db = await getDatabase(options.config); const migration = new index_1.database.MigrationManager(db, { migrationsDir: options.migrationsDir }); const all = migration.getMigrationFiles(); const executed = await migration.getExecutedMigrations(); console.log(chalk_1.default.bold('\nšŸ“‹ Migration Status')); console.log(chalk_1.default.gray('─'.repeat(50))); if (all.length === 0) { console.log(chalk_1.default.yellow('No migrations found')); } else { all.forEach(m => { const isExecuted = executed.includes(m.name); const status = isExecuted ? chalk_1.default.green('āœ… Done') : chalk_1.default.yellow('ā³ Pending'); console.log(` ${status} ${chalk_1.default.white(m.name)}`); }); console.log(chalk_1.default.gray('\n' + '─'.repeat(50))); console.log(` Total: ${all.length} | Executed: ${chalk_1.default.green(executed.length)} | Pending: ${chalk_1.default.yellow(all.length - executed.length)}`); } await db.close(); } catch (error) { console.error(chalk_1.default.red(`Error: ${error.message}`)); process.exit(1); } }); program.command('make:migration') .description('Generate a new migration template') .argument('<name>', 'Name of the migration') .option('-d, --migrations-dir <path>', 'Migrations directory path', './migrations') .action((name, options) => { const timestamp = Date.now(); const fileName = `${timestamp}_${name}.ts`; const migrationsDir = path_1.default.join(process.cwd(), options.migrationsDir); if (!fs_1.default.existsSync(migrationsDir)) { fs_1.default.mkdirSync(migrationsDir, { recursive: true }); } const template = `export const up = async (db: any) => { // Write your migration logic here }; export const down = async (db: any) => { // Write your rollback logic here }; `; fs_1.default.writeFileSync(path_1.default.join(migrationsDir, fileName), template); console.log(chalk_1.default.green(`\nāœ… Migration created: ./${options.migrationsDir}/${fileName}`)); }); program.command('zpack:vacuum') .description('Compact a ZPack binary file to save disk space') .argument('<file>', 'ZPack file path') .action(async (file) => { const spinner = (0, ora_1.default)(`Vacuuming ${file}...`).start(); const startSize = fs_1.default.existsSync(file) ? fs_1.default.statSync(file).size : 0; try { const db = index_1.database.createDatabase({ adapter: 'zpack', config: { path: file } }); await db.vacuum(); await db.close(); const endSize = fs_1.default.statSync(file).size; const reduction = startSize > 0 ? ((1 - endSize / startSize) * 100) : 0; spinner.succeed(chalk_1.default.green(`āœ… Vacuum complete for ${file}`)); console.log(chalk_1.default.gray(` Original Size: ${formatBytes(startSize)}`)); console.log(chalk_1.default.gray(` New Size: ${formatBytes(endSize)}`)); console.log(chalk_1.default.bold.blue(` Efficiency: ${reduction.toFixed(1)}% reduction`)); } catch (error) { spinner.fail(chalk_1.default.red(`āŒ Vacuum failed: ${error.message}`)); process.exit(1); } }); function formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } program.command('db:backup') .description('Backup database to timestamped file') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-o, --output <dir>', 'Output directory for backups', './backups') .action(async (options) => { const spinner = (0, ora_1.default)('Creating backup...').start(); try { const db = await getDatabase(options.config); const config = loadConfig(options.config); const backupDir = path_1.default.resolve(process.cwd(), options.output); if (!fs_1.default.existsSync(backupDir)) { fs_1.default.mkdirSync(backupDir, { recursive: true }); } const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const backupFile = path_1.default.join(backupDir, `backup_${timestamp}.zerohelper.json`); let tables = []; if (config.adapter === 'json' && config.config.path) { const dbPath = path_1.default.resolve(process.cwd(), config.config.path); if (fs_1.default.existsSync(dbPath)) { const dbContent = JSON.parse(fs_1.default.readFileSync(dbPath, 'utf-8')); tables = Object.keys(dbContent); } } else if (config.adapter === 'sqlite' && config.config.path) { tables = await db.tables?.() || []; } else if (config.adapter === 'zpack' && config.config.path) { tables = await db.tables?.() || []; } else { tables = ['users', 'products', 'orders', 'migrations', 'migration_test', 'test_backup']; } const backupData = { version: package_json_1.version, timestamp: new Date().toISOString(), config: config, data: {} }; for (const table of tables) { try { const records = await db.select(table); if (records.length > 0) { backupData.data[table] = records; } } catch (err) { // Table doesn't exist or can't be accessed } } fs_1.default.writeFileSync(backupFile, JSON.stringify(backupData, null, 2)); const fileSize = fs_1.default.statSync(backupFile).size; spinner.succeed(chalk_1.default.green(`āœ… Backup created: ${backupFile}`)); console.log(chalk_1.default.gray(` Size: ${formatBytes(fileSize)}`)); console.log(chalk_1.default.gray(` Tables: ${Object.keys(backupData.data).join(', ') || 'none'}`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Backup failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('db:restore') .description('Restore database from backup file') .argument('<backup-file>', 'Path to backup file') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .action(async (backupFile, options) => { if (!fs_1.default.existsSync(backupFile)) { console.error(chalk_1.default.red(`Error: Backup file not found: ${backupFile}`)); process.exit(1); } const backupData = JSON.parse(fs_1.default.readFileSync(backupFile, 'utf-8')); const { confirm } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirm', message: chalk_1.default.yellow(`āš ļø This will restore data from backup. Are you sure?`), default: false } ]); if (!confirm) { console.log(chalk_1.default.yellow('Restore cancelled')); return; } const spinner = (0, ora_1.default)('Restoring database...').start(); try { const db = await getDatabase(options.config); for (const [table, records] of Object.entries(backupData.data)) { const rows = records; if (Array.isArray(rows) && rows.length > 0) { try { await db.bulkInsert(table, rows); spinner.text = `āœ… ${table}: ${rows.length} records`; } catch (err) { spinner.text = `āš ļø ${table}: failed`; } } } spinner.succeed(chalk_1.default.green(`āœ… Database restored from ${backupFile}`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Restore failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('cache:clear') .description('Clear all cache') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .action(async (options) => { const spinner = (0, ora_1.default)('Clearing cache...').start(); try { const db = await getDatabase(options.config); const metrics = db.getMetrics(); const beforeHits = metrics.cache?.hits || 0; const beforeMisses = metrics.cache?.misses || 0; // Clear cache by triggering cache invalidation await db.insert('_cache_clear', { timestamp: Date.now() }); await db.delete('_cache_clear', { timestamp: Date.now() }); const afterMetrics = db.getMetrics(); const afterHits = afterMetrics.cache?.hits || 0; const afterMisses = afterMetrics.cache?.misses || 0; spinner.succeed(chalk_1.default.green('āœ… Cache cleared')); console.log(chalk_1.default.gray(` Previous hits: ${beforeHits}`)); console.log(chalk_1.default.gray(` Previous misses: ${beforeMisses}`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Cache clear failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('cache:stats') .description('Show detailed cache statistics') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .action(async (options) => { try { const db = await getDatabase(options.config); const metrics = db.getMetrics(); console.log(chalk_1.default.bold('\nšŸ“Š Cache Statistics')); console.log(chalk_1.default.gray('─'.repeat(50))); if (!metrics.cache) { console.log(chalk_1.default.yellow(' No cache statistics available')); console.log(chalk_1.default.gray(' Cache may not be enabled for this adapter')); } else { const hits = metrics.cache.hits || 0; const misses = metrics.cache.misses || 0; const total = hits + misses; const hitRate = total > 0 ? (hits / total) * 100 : 0; console.log(chalk_1.default.cyan(' Cache Hits:'), chalk_1.default.green(hits.toLocaleString())); console.log(chalk_1.default.cyan(' Cache Misses:'), chalk_1.default.red(misses.toLocaleString())); console.log(chalk_1.default.cyan(' Total Requests:'), chalk_1.default.white(total.toLocaleString())); console.log(chalk_1.default.cyan(' Hit Rate:'), chalk_1.default.white(`${hitRate.toFixed(2)}%`)); let healthStatus = 'āŒ'; let healthColor = chalk_1.default.red; if (hitRate >= 90) { healthStatus = 'āœ… Excellent'; healthColor = chalk_1.default.green; } else if (hitRate >= 70) { healthStatus = 'āš ļø Good'; healthColor = chalk_1.default.yellow; } else if (hitRate >= 50) { healthStatus = 'āš ļø Fair'; healthColor = chalk_1.default.yellow; } else { healthStatus = 'āŒ Poor'; healthColor = chalk_1.default.red; } console.log(chalk_1.default.cyan(' Health:'), healthColor(healthStatus)); if (hitRate < 70) { console.log(chalk_1.default.gray('\n šŸ’” Tip: Consider increasing cache TTL for better hit rate')); } } console.log(chalk_1.default.gray('\n' + '─'.repeat(50))); await db.close(); } catch (error) { console.error(chalk_1.default.red(`Error: ${error.message}`)); process.exit(1); } }); program.command('repl') .description('Interactive ZeroHelper REPL mode') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .action(async (options) => { console.log(chalk_1.default.bold.cyan('\nšŸ”§ ZeroHelper REPL Mode\n')); console.log(chalk_1.default.gray('Type .exit to quit, .help for commands\n')); try { const db = await getDatabase(options.config); const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: chalk_1.default.blue('zero> ') }); rl.prompt(); rl.on('line', async (line) => { const cmd = line.trim(); if (cmd === '.exit') { await db.close(); rl.close(); console.log(chalk_1.default.green('\nšŸ‘‹ Goodbye!\n')); process.exit(0); } else if (cmd === '.help') { console.log(chalk_1.default.bold('\nAvailable REPL Commands:')); console.log(' .exit Exit REPL'); console.log(' .help Show this help'); console.log(' .stats Show database stats'); console.log(' .metrics Show performance metrics'); console.log(' select <table> Select all from table'); console.log(' count <table> Count records in table'); console.log(' clear Clear screen\n'); } else if (cmd === '.stats') { const metrics = db.getMetrics(); console.log(chalk_1.default.bold('\nšŸ“Š Database Stats:')); console.log(` Operations: ${metrics.database.count || 0}`); console.log(` Avg Latency: ${(metrics.database.averageDuration || 0).toFixed(2)}ms\n`); } else if (cmd === '.metrics') { const metrics = db.getMetrics(); console.log(chalk_1.default.bold('\nšŸ“Š Full Metrics:')); console.log(JSON.stringify(metrics, null, 2)); console.log(''); } else if (cmd === '.clear') { console.clear(); } else if (cmd.startsWith('select ')) { const table = cmd.replace('select ', '').trim(); try { const records = await db.select(table); console.log(chalk_1.default.bold(`\nFound ${records.length} records in ${table}:`)); console.log(JSON.stringify(records, null, 2)); console.log(''); } catch (err) { console.error(chalk_1.default.red(`Error: ${err.message}\n`)); } } else if (cmd.startsWith('count ')) { const table = cmd.replace('count ', '').trim(); try { const records = await db.select(table); console.log(chalk_1.default.bold(`\n${table}: ${records.length} records\n`)); } catch (err) { console.error(chalk_1.default.red(`Error: ${err.message}\n`)); } } else if (cmd.length > 0) { console.log(chalk_1.default.yellow('Unknown command. Type .help for available commands\n')); } rl.prompt(); }); rl.on('close', () => { db.close(); console.log(chalk_1.default.green('\nšŸ‘‹ Goodbye!\n')); process.exit(0); }); } catch (error) { console.error(chalk_1.default.red(`Error: ${error.message}`)); process.exit(1); } }); program.command('db:export') .description('Export table data to file') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-t, --table <name>', 'Table name to export') .option('-f, --format <format>', 'Output format (json|csv)', 'json') .option('-o, --output <file>', 'Output file path') .action(async (options) => { if (!options.table) { console.error(chalk_1.default.red('Error: --table option is required')); process.exit(1); } const spinner = (0, ora_1.default)(`Exporting ${options.table}...`).start(); try { const db = await getDatabase(options.config); const records = await db.select(options.table); if (records.length === 0) { spinner.warn(chalk_1.default.yellow(`āš ļø No records found in ${options.table}`)); await db.close(); return; } const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const ext = options.format === 'csv' ? 'csv' : 'json'; const defaultFile = `${options.table}_export_${timestamp}.${ext}`; const outputFile = options.output || path_1.default.join(process.cwd(), 'exports', defaultFile); const outputDir = path_1.default.dirname(outputFile); if (!fs_1.default.existsSync(outputDir)) { fs_1.default.mkdirSync(outputDir, { recursive: true }); } let content = ''; if (options.format === 'csv') { const headers = Object.keys(records[0]); const csvRows = [ headers.join(','), ...records.map((row) => headers.map(h => { const val = row[h]; return typeof val === 'string' && val.includes(',') ? `"${val}"` : val; }).join(',')) ]; content = csvRows.join('\n'); } else { content = JSON.stringify(records, null, 2); } fs_1.default.writeFileSync(outputFile, content); const fileSize = fs_1.default.statSync(outputFile).size; spinner.succeed(chalk_1.default.green(`āœ… Exported ${records.length} records to ${outputFile}`)); console.log(chalk_1.default.gray(` Size: ${formatBytes(fileSize)}`)); console.log(chalk_1.default.gray(` Format: ${options.format.toUpperCase()}`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Export failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.command('db:import') .description('Import data from file to table') .argument('<file>', 'Input file path') .option('-c, --config <path>', 'Path to config file', 'zero.config.ts') .option('-t, --table <name>', 'Table name') .option('-f, --format <format>', 'Input format (json|csv)', 'json') .action(async (inputFile, options) => { if (!options.table) { console.error(chalk_1.default.red('Error: --table option is required')); process.exit(1); } if (!fs_1.default.existsSync(inputFile)) { console.error(chalk_1.default.red(`Error: File not found: ${inputFile}`)); process.exit(1); } const { confirm } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirm', message: chalk_1.default.yellow(`āš ļø This will import data to ${options.table}. Are you sure?`), default: false } ]); if (!confirm) { console.log(chalk_1.default.yellow('Import cancelled')); return; } const spinner = (0, ora_1.default)(`Importing to ${options.table}...`).start(); try { const db = await getDatabase(options.config); const content = fs_1.default.readFileSync(inputFile, 'utf-8'); let data = []; if (options.format === 'csv') { const lines = content.trim().split('\n'); const headers = lines[0].split(','); data = lines.slice(1).map((line) => { const values = line.split(','); const row = {}; headers.forEach((h, i) => { row[h] = values[i]?.replace(/"/g, '').trim(); }); return row; }); } else { data = JSON.parse(content); } const count = await db.bulkInsert(options.table, data); spinner.succeed(chalk_1.default.green(`āœ… Imported ${count} records to ${options.table}`)); await db.close(); } catch (error) { spinner.fail(chalk_1.default.red('āŒ Import failed')); console.error(chalk_1.default.red(error.message)); process.exit(1); } }); program.parse();