mwalajs 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mwala.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync } from 'child_process';
3
+ import { spawn } from 'child_process';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { fileURLToPath, pathToFileURL } from 'url';
@@ -9,19 +9,20 @@ import readlineSync from 'readline-sync';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
11
11
 
12
+ const args = process.argv.slice(2);
13
+ const command = args[0];
14
+
12
15
  // Dynamic imports
13
16
  const { getDbConnection } = await import(pathToFileURL(path.join(__dirname, '../config/createdatabase.mjs')).href);
14
- const { createTable, dropTable, migrateAll, rollbackLastMigration } = await import(pathToFileURL(path.join(__dirname, '../runMigrations.mjs')).href);
15
17
  const { setupMwalajs } = await import(pathToFileURL(path.join(__dirname, '../setupMwalajs.mjs')).href);
16
18
  const { createProject } = await import(pathToFileURL(path.join(__dirname, '../createProject.mjs')).href);
17
19
 
18
- // Import new DB helpers (we'll define these later)
19
- const {
20
- listTables,
21
- describeTable,
22
- truncateTable,
23
- renameTable,
24
- backupDatabase,
20
+ const {
21
+ listTables,
22
+ describeTable,
23
+ truncateTable,
24
+ renameTable,
25
+ backupDatabase,
25
26
  restoreDatabase,
26
27
  seedDatabase,
27
28
  showDatabaseSize,
@@ -37,88 +38,74 @@ const {
37
38
  showConnections,
38
39
  killConnections,
39
40
  checkTableExists
40
- } = await import(pathToFileURL(path.join(__dirname, '../dbUtils.mjs')).href);
41
-
42
- const args = process.argv.slice(2);
43
- const command = args[0];
41
+ } = await import(pathToFileURL(path.join(__dirname, '../utils/dbUtils.mjs')).href);
44
42
 
45
43
  if (!command || command === 'help' || command === 'h') {
46
44
  console.log(`
47
- ╔══════════════════════════════════════════════════╗
48
- MwalaJS CLI v1.1.0
49
- ╚══════════════════════════════════════════════════╝
50
-
51
- General Commands:
52
- - mwala -v | --version → Show version
53
- - mwala help | h → Show this help
54
-
55
- Project Management:
56
- - mwala create-project → Create new project
57
- - mwala init → Initialize MwalaJS
58
-
59
- Run Application:
60
- - mwala serve | app.mjs → Start server
61
-
62
- Code Generation:
63
- - mwala generate model <name>
64
- - mwala generate controller <name>
65
- - mwala generate route <name>
66
- - mwala generate view <name>
67
- - mwala generate midware <name>
68
-
69
- ════════════════════════════════════════════════════
70
- DATABASE COMMANDS
71
- ════════════════════════════════════════════════════
72
-
73
- Setup & Config:
74
- - mwala create-db Create/connect database (interactive)
75
- - mwala db:config Reconfigure .env database settings
76
-
77
- Table Management:
78
- - mwala db:table list List all tables
79
- - mwala db:table create <name> Create table
80
- - mwala db:table drop <name> Drop table
81
- - mwala db:table truncate <name> → Truncate table
82
- - mwala db:table rename <old> <new> Rename table
83
- - mwala db:table copy <src> <dest> Copy table structure + data
84
- - mwala db:table exists <name> Check if table exists
85
- - mwala db:table describe <name> Show table structure
86
- - mwala db:table count <name> Count rows in table
87
-
88
- Migrations:
89
- - mwala migrate all Run all pending migrations
90
- - mwala rollback last → Rollback last migration
91
- - mwala rollback all Rollback all migrations
92
-
93
- Data & Backup:
94
- - mwala db:seed <file.js> → Run seed file
95
- - mwala db:backup → Backup database
96
- - mwala db:restore <file.sql> Restore from backup
97
- - mwala db:export <table> <file.csv> Export table to CSV
98
- - mwala db:import <file.csv> <table> → Import CSV into table
99
-
100
- Maintenance & Stats:
101
- - mwala db:size → Show database size
102
- - mwala db:indexes <table> → List indexes on table
103
- - mwala db:analyze <table> → Analyze table (PostgreSQL)
104
- - mwala db:reindex <table> → Rebuild indexes
105
- - mwala db:vacuum → Vacuum database (SQLite/PostgreSQL)
106
- - mwala db:connections → Show active connections
107
- - mwala db:kill-connections → Kill all other connections (admin)
108
- - mwala db:drop-all-tables → ⚠️ Drop ALL tables (dangerous!)
109
-
110
- Use: mwala <command> [options]
45
+ ╔══════════════════════════════════════════════════════════╗
46
+ MwalaJS CLI v1.2.0
47
+ ╚══════════════════════════════════════════════════════════╝
48
+
49
+ General:
50
+ mwala -v | --version → Show version
51
+ mwala help | h → Show this help
52
+
53
+ Project:
54
+ mwala create-project → Create new project
55
+ mwala init → Initialize MwalaJS
56
+
57
+ Run:
58
+ mwala serve Start the server (runs app.mjs)
59
+
60
+ Generate:
61
+ mwala generate model <name>
62
+ mwala generate controller <name>
63
+ mwala generate route <name>
64
+ mwala generate view <name>
65
+ mwala generate midware <name>
66
+
67
+ Database Setup:
68
+ mwala create-db → Interactive DB setup & creation
69
+ mwala db:config → Reconfigure database (.env)
70
+
71
+ Database Commands:
72
+ mwala db:table list List all tables
73
+ mwala db:table describe <name> Show table structure
74
+ mwala db:table count <name> → Count rows
75
+ mwala db:table truncate <name> → Empty table
76
+ mwala db:table rename <old> <new> Rename table
77
+ mwala db:table copy <src> <dest> Copy table
78
+ mwala db:table exists <name> Check if exists
79
+
80
+ mwala db:backup Backup database
81
+ mwala db:restore <file.sql> Restore from backup
82
+ mwala db:size Show database size
83
+ mwala db:indexes <table> List indexes
84
+ mwala db:vacuum Optimize (SQLite/PostgreSQL)
85
+ mwala db:connections → Show active connections
86
+ mwala db:kill-connections → Kill other connections (admin)
87
+ mwala db:drop-all-tables DROP ALL TABLES!
88
+
89
+ mwala db:export <table> <file.csv> Export to CSV
90
+ mwala db:import <file.csv> <table> → Import from CSV
91
+ mwala db:seed <seedFile.mjs> → Run seed file
92
+
93
+ Migrations:
94
+ mwala migrate all Run all migrations
95
+ mwala rollback last Rollback last migration
96
+
97
+ Enjoy building with MwalaJS!
111
98
  `);
112
99
  process.exit(0);
113
100
  }
114
101
 
102
+ // ====================== COMMAND SWITCH ======================
115
103
  switch (command) {
116
-
117
- case 'version':
118
104
  case '-v':
105
+ case 'version':
119
106
  case '--version':
120
107
  console.log('MwalaJS Version: 1.1.0');
121
- break;
108
+ process.exit(0);
122
109
 
123
110
  case 'create-project':
124
111
  createProject();
@@ -129,143 +116,178 @@ switch (command) {
129
116
  break;
130
117
 
131
118
  case 'serve':
132
- case 'app.mjs':
133
- const { spawn } = require('child_process');
134
- const child = spawn('node', ['app.mjs'], { stdio: 'inherit', cwd: process.cwd() });
119
+ const child = spawn('node', ['app.mjs'], {
120
+ stdio: 'inherit',
121
+ cwd: process.cwd()
122
+ });
135
123
 
136
124
  process.on('SIGINT', () => child.kill('SIGINT'));
137
125
  process.on('SIGTERM', () => child.kill('SIGTERM'));
138
126
 
139
- child.on('exit', (code) => process.exit(code || 0));
127
+ child.on('exit', (code) => process.exit(code ?? 0));
140
128
  child.on('error', (err) => {
141
129
  console.error(`Failed to start app: ${err.message}`);
142
130
  process.exit(1);
143
131
  });
144
132
  break;
145
133
 
146
- case 'generate':
147
- // ... (your existing generate logic remains unchanged)
148
- // (kept same as before for brevity)
134
+ case 'generate': {
135
+ const type = args[1]?.toLowerCase();
136
+ const name = args[2];
137
+
138
+ if (!type || !name) {
139
+ console.error('Usage: mwala generate <model|controller|route|view|midware> <name>');
140
+ process.exit(1);
141
+ }
142
+
143
+ const folders = {
144
+ model: 'models',
145
+ controller: 'controllers',
146
+ route: 'routes',
147
+ view: 'views',
148
+ midware: 'middlewares'
149
+ };
150
+
151
+ if (!folders[type]) {
152
+ console.error(`Invalid type. Use: ${Object.keys(folders).join(', ')}`);
153
+ process.exit(1);
154
+ }
155
+
156
+ const filePath = path.join(process.cwd(), folders[type], `${name}.mjs`);
157
+
158
+ if (fs.existsSync(filePath)) {
159
+ console.error(`${name} ${type} already exists.`);
160
+ process.exit(1);
161
+ }
162
+
163
+ let content = '';
164
+ switch (type) {
165
+ case 'model':
166
+ content = `export const ${name}Model = {};\n`;
167
+ break;
168
+ case 'controller':
169
+ content = `export const ${name}Controller = {\n getPage: (req, res) => {\n res.render('${name}', { title: '${name}' });\n }\n};\n`;
170
+ break;
171
+ case 'route':
172
+ content = `import mwalajs from 'mwalajs';\nimport { ${name}Controller } from '../controllers/${name}.mjs';\n\nconst router = mwalajs.Router();\nrouter.get('/', ${name}Controller.getPage);\nexport { router as ${name}Route };\n`;
173
+ break;
174
+ case 'view':
175
+ content = `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <title>${name}</title>\n</head>\n<body>\n <h1>Welcome to ${name} Page</h1>\n</body>\n</html>\n`;
176
+ break;
177
+ case 'midware':
178
+ content = `export const ${name} = (req, res, next) => {\n next();\n};\n`;
179
+ break;
180
+ }
181
+
182
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
183
+ fs.writeFileSync(filePath, content);
184
+ console.log(`✅ ${name} ${type} created at ${folders[type]}/${name}.mjs`);
149
185
  break;
186
+ }
150
187
 
151
- // ====================== DATABASE COMMANDS ======================
188
+ // ==================== DATABASE COMMANDS ====================
152
189
 
153
190
  case 'create-db':
154
- getDbConnection().then(() => console.log('✅ Database ready.')).catch(err => {
155
- console.error(`❌ Failed: ${err.message}`);
156
- process.exit(1);
157
- });
191
+ getDbConnection()
192
+ .then(() => console.log('✅ Database configured and connected successfully!'))
193
+ .catch(err => {
194
+ console.error('❌ Database setup failed:', err.message);
195
+ process.exit(1);
196
+ });
158
197
  break;
159
198
 
160
199
  case 'db:config':
161
- getDbConnection(); // Re-runs interactive setup
200
+ getDbConnection();
162
201
  break;
163
202
 
164
203
  case 'db:table':
165
- const sub = args[1];
204
+ const action = args[1];
166
205
  const tableName = args[2];
167
- const extra = args[3];
206
+ const extraArg = args[3];
168
207
 
169
- if (!sub) {
170
- console.log('Usage: mwala db:table <list|create|drop|truncate|rename|copy|exists|describe|count> [args]');
208
+ if (!action) {
209
+ console.error('Usage: mwala db:table <list|describe|count|truncate|rename|copy|exists> [args]');
171
210
  process.exit(1);
172
211
  }
173
212
 
174
- switch (sub) {
213
+ switch (action) {
175
214
  case 'list':
176
215
  listTables();
177
216
  break;
178
- case 'create':
179
- if (!tableName) return console.error('Table name required');
180
- createTable(tableName);
217
+ case 'describe':
218
+ if (!tableName) {
219
+ console.error('Table name required');
220
+ process.exit(1);
221
+ }
222
+ describeTable(tableName);
181
223
  break;
182
- case 'drop':
183
- if (!tableName) return console.error('Table name required');
184
- dropTable(tableName);
224
+ case 'count':
225
+ if (!tableName) {
226
+ console.error('Table name required');
227
+ process.exit(1);
228
+ }
229
+ countRows(tableName);
185
230
  break;
186
231
  case 'truncate':
187
- if (!tableName) return console.error('Table name required');
188
- truncateTable(tableName);
232
+ if (!tableName) {
233
+ console.error('Table name required');
234
+ process.exit(1);
235
+ }
236
+ if (readlineSync.keyInYN(`Truncate ${tableName}? This cannot be undone.`)) {
237
+ truncateTable(tableName);
238
+ }
189
239
  break;
190
240
  case 'rename':
191
- if (!tableName || !extra) return console.error('Usage: rename <old> <new>');
192
- renameTable(tableName, extra);
241
+ if (!tableName || !extraArg) {
242
+ console.error('Usage: mwala db:table rename <oldName> <newName>');
243
+ process.exit(1);
244
+ }
245
+ renameTable(tableName, extraArg);
193
246
  break;
194
247
  case 'copy':
195
- if (!tableName || !extra) return console.error('Usage: copy <source> <destination>');
196
- copyTable(tableName, extra);
248
+ if (!tableName || !extraArg) {
249
+ console.error('Usage: mwala db:table copy <source> <destination>');
250
+ process.exit(1);
251
+ }
252
+ copyTable(tableName, extraArg);
197
253
  break;
198
254
  case 'exists':
199
- if (!tableName) return console.error('Table name required');
255
+ if (!tableName) {
256
+ console.error('Table name required');
257
+ process.exit(1);
258
+ }
200
259
  checkTableExists(tableName);
201
260
  break;
202
- case 'describe':
203
- if (!tableName) return console.error('Table name required');
204
- describeTable(tableName);
205
- break;
206
- case 'count':
207
- if (!tableName) return console.error('Table name required');
208
- countRows(tableName);
209
- break;
210
261
  default:
211
- console.log('Invalid db:table subcommand');
262
+ console.error(`Unknown db:table action: ${action}`);
263
+ process.exit(1);
212
264
  }
213
265
  break;
214
266
 
215
- case 'migrate':
216
- if (args[1] === 'all') migrateAll();
217
- else console.error('Use: mwala migrate all');
218
- break;
219
-
220
- case 'rollback':
221
- if (args[1] === 'last') rollbackLastMigration();
222
- else if (args[1] === 'all') dropAllTables(); // or full rollback
223
- else console.error('Use: mwala rollback last');
224
- break;
225
-
226
- case 'db:seed':
227
- if (!args[1]) return console.error('Seed file required');
228
- seedDatabase(args[1]);
229
- break;
230
-
231
267
  case 'db:backup':
232
268
  backupDatabase();
233
269
  break;
234
270
 
235
271
  case 'db:restore':
236
- if (!args[1]) return console.error('Backup file required');
272
+ if (!args[1]) {
273
+ console.error('Usage: mwala db:restore <backup-file.sql>');
274
+ process.exit(1);
275
+ }
237
276
  restoreDatabase(args[1]);
238
277
  break;
239
278
 
240
- case 'db:export':
241
- if (!args[1] || !args[2]) return console.error('Usage: db:export <table> <file.csv>');
242
- exportTableToCsv(args[1], args[2]);
243
- break;
244
-
245
- case 'db:import':
246
- if (!args[1] || !args[2]) return console.error('Usage: db:import <file.csv> <table>');
247
- importCsvToTable(args[1], args[2]);
248
- break;
249
-
250
279
  case 'db:size':
251
280
  showDatabaseSize();
252
281
  break;
253
282
 
254
283
  case 'db:indexes':
255
- if (!args[1]) return console.error('Table name required');
284
+ if (!args[1]) {
285
+ console.error('Table name required');
286
+ process.exit(1);
287
+ }
256
288
  listIndexes(args[1]);
257
289
  break;
258
290
 
259
- case 'db:analyze':
260
- if (!args[1]) return console.error('Table name required');
261
- analyzeTable(args[1]);
262
- break;
263
-
264
- case 'db:reindex':
265
- if (!args[1]) return console.error('Table name required');
266
- reindexTable(args[1]);
267
- break;
268
-
269
291
  case 'db:vacuum':
270
292
  vacuumDatabase();
271
293
  break;
@@ -275,18 +297,49 @@ switch (command) {
275
297
  break;
276
298
 
277
299
  case 'db:kill-connections':
278
- if (readlineSync.keyInYN('⚠️ Kill all other connections?')) {
300
+ if (readlineSync.keyInYN(' Kill all other database connections?')) {
279
301
  killConnections();
302
+ } else {
303
+ console.log('Cancelled.');
280
304
  }
281
305
  break;
282
306
 
283
307
  case 'db:drop-all-tables':
284
- if (readlineSync.keyInYN('⚠️⚠️ This will DROP ALL TABLES! Continue?')) {
285
- dropAllTables();
308
+ if (readlineSync.keyInYN(' DROP ALL TABLES PERMANENTLY? Type YES to confirm:')) {
309
+ if (readlineSync.question('Type database name to confirm: ') === process.env.DB_NAME) {
310
+ dropAllTables();
311
+ } else {
312
+ console.log('Database name mismatch. Aborted.');
313
+ }
286
314
  }
287
315
  break;
288
316
 
317
+ case 'db:export':
318
+ if (!args[1] || !args[2]) {
319
+ console.error('Usage: mwala db:export <table> <output.csv>');
320
+ process.exit(1);
321
+ }
322
+ exportTableToCsv(args[1], args[2]);
323
+ break;
324
+
325
+ case 'db:import':
326
+ if (!args[1] || !args[2]) {
327
+ console.error('Usage: mwala db:import <input.csv> <table>');
328
+ process.exit(1);
329
+ }
330
+ importCsvToTable(args[1], args[2]);
331
+ break;
332
+
333
+ case 'db:seed':
334
+ if (!args[1]) {
335
+ console.error('Usage: mwala db:seed <seedFile.mjs>');
336
+ process.exit(1);
337
+ }
338
+ seedDatabase(args[1]);
339
+ break;
340
+
289
341
  default:
290
- console.error(`Unknown command: ${command}\nRun "mwala help" for list.`);
342
+ console.error(`Unknown command: ${command}`);
343
+ console.log('Run "mwala help" for available commands.');
291
344
  process.exit(1);
292
345
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mwalajs",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "MwalaJS Framework CLI Tool and Web Framework for Backend and Frontend Development.",
5
5
  "type": "module",
6
6
  "main": "app.mjs",
package/utils/dbUtils.mjs CHANGED
@@ -1,124 +1,517 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import { execSync } from 'child_process';
4
+ import { createWriteStream, createReadStream } from 'fs';
5
+ import csv from 'csv-parser';
6
+ import { stringify } from 'csv-stringify';
3
7
  import dotenv from 'dotenv';
4
8
  import mysql from 'mysql2/promise';
5
9
  import { Client } from 'pg';
6
10
  import { MongoClient } from 'mongodb';
7
11
  import sqlite3 from 'sqlite3';
8
- import { execSync } from 'child_process';
9
12
  import readlineSync from 'readline-sync';
13
+ import { fileURLToPath } from 'url';
10
14
 
15
+ // Load .env
11
16
  dotenv.config();
12
17
 
13
- const { DB_TYPE, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME } = process.env;
18
+ const { DB_TYPE, DB_HOST = 'localhost', DB_USER, DB_PASSWORD, DB_NAME, DB_PORT } = process.env;
19
+
20
+ // Helper to check if DB is configured
21
+ const isDbConfigured = () => {
22
+ return DB_TYPE && DB_NAME && (DB_TYPE === 'sqlite' || (DB_USER && DB_PASSWORD));
23
+ };
24
+
25
+ const suggestDbSetup = () => {
26
+ console.log('\n⚠️ Database not configured or connection failed.');
27
+ console.log(' Run: mwala create-db → to set up your database interactively');
28
+ console.log(' Or manually create a .env file with:');
29
+ console.log(' DB_TYPE=mysql|postgresql|sqlite|mongodb');
30
+ console.log(' DB_NAME=your_db_name');
31
+ console.log(' DB_HOST=localhost');
32
+ console.log(' DB_USER=your_user');
33
+ console.log(' DB_PASSWORD=your_pass\n');
34
+ };
14
35
 
36
+ // Global connection cache
15
37
  let connection = null;
16
38
 
17
39
  const getConnection = async () => {
40
+ if (!isDbConfigured()) {
41
+ suggestDbSetup();
42
+ process.exit(1);
43
+ }
44
+
18
45
  if (connection) return connection;
19
46
 
20
- switch (DB_TYPE) {
21
- case 'mysql':
47
+ try {
48
+ if (DB_TYPE === 'mysql') {
22
49
  connection = await mysql.createConnection({
23
- host: DB_HOST, user: DB_USER, password: DB_PASSWORD, database: DB_NAME
50
+ host: DB_HOST,
51
+ user: DB_USER,
52
+ password: DB_PASSWORD,
53
+ database: DB_NAME,
54
+ port: DB_PORT || 3306
55
+ });
56
+ } else if (DB_TYPE === 'postgresql') {
57
+ connection = new Client({
58
+ host: DB_HOST,
59
+ user: DB_USER,
60
+ password: DB_PASSWORD,
61
+ database: DB_NAME,
62
+ port: DB_PORT || 5432
24
63
  });
25
- break;
26
- case 'postgresql':
27
- connection = new Client({ host: DB_HOST, user: DB_USER, password: DB_PASSWORD, database: DB_NAME });
28
64
  await connection.connect();
29
- break;
30
- case 'mongodb':
31
- connection = await MongoClient.connect(`mongodb://${DB_HOST}:27017/${DB_NAME}`);
32
- break;
33
- case 'sqlite':
65
+ } else if (DB_TYPE === 'sqlite') {
34
66
  connection = new sqlite3.Database(`./${DB_NAME}.sqlite`);
35
- break;
67
+ connection.serialize();
68
+ } else if (DB_TYPE === 'mongodb') {
69
+ const client = await MongoClient.connect(`mongodb://${DB_HOST}:${DB_PORT || 27017}`);
70
+ connection = client.db(DB_NAME);
71
+ } else {
72
+ console.error(' Unsupported DB_TYPE:', DB_TYPE);
73
+ process.exit(1);
74
+ }
75
+ } catch (err) {
76
+ console.error(' Failed to connect to database:', err.message);
77
+ suggestDbSetup();
78
+ process.exit(1);
36
79
  }
80
+
37
81
  return connection;
38
82
  };
39
83
 
84
+ // ====================== IMPLEMENTED COMMANDS ======================
85
+
40
86
  export const listTables = async () => {
41
87
  const conn = await getConnection();
88
+
42
89
  if (DB_TYPE === 'mysql') {
43
90
  const [rows] = await conn.query('SHOW TABLES');
44
- console.log('Tables:', rows.map(r => Object.values(r)[0]));
45
- } else if (DB_TYPE === 'postgresql') {
46
- const res = await conn.query(`SELECT tablename FROM pg_tables WHERE schemaname = 'public'`);
47
- console.log('Tables:', res.rows.map(r => r.tablename));
48
- } else if (DB_TYPE === 'sqlite') {
49
- conn.all("SELECT name FROM sqlite_master WHERE type='table'", (err, rows) => {
50
- console.log('Tables:', rows.map(r => r.name));
91
+ const tables = rows.map(r => Object.values(r)[0]);
92
+ console.log('📋 Tables in database:');
93
+ tables.forEach(t => console.log(` ${t}`));
94
+ if (tables.length === 0) console.log(' (No tables found)');
95
+ }
96
+ else if (DB_TYPE === 'postgresql') {
97
+ const res = await conn.query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
98
+ console.log('📋 Tables:');
99
+ res.rows.forEach(r => console.log(` • ${r.tablename}`));
100
+ }
101
+ else if (DB_TYPE === 'sqlite') {
102
+ return new Promise((resolve) => {
103
+ conn.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'", (err, rows) => {
104
+ if (err) return console.error(err.message);
105
+ console.log('📋 Tables:');
106
+ rows.forEach(r => console.log(` • ${r.name}`));
107
+ if (rows.length === 0) console.log(' (No tables found)');
108
+ resolve();
109
+ });
51
110
  });
111
+ }
112
+ else if (DB_TYPE === 'mongodb') {
113
+ const collections = await conn.listCollections().toArray();
114
+ console.log('📋 Collections:');
115
+ collections.forEach(c => console.log(` • ${c.name}`));
52
116
  }
53
117
  };
54
118
 
55
119
  export const describeTable = async (table) => {
56
120
  const conn = await getConnection();
121
+
57
122
  if (DB_TYPE === 'mysql') {
58
123
  const [rows] = await conn.query(`DESCRIBE \`${table}\``);
59
124
  console.table(rows);
60
- } else if (DB_TYPE === 'postgresql') {
125
+ }
126
+ else if (DB_TYPE === 'postgresql') {
61
127
  const res = await conn.query(`
62
- SELECT column_name, data_type, is_nullable
128
+ SELECT column_name, data_type, is_nullable, column_default
63
129
  FROM information_schema.columns
64
- WHERE table_name = $1`, [table]);
130
+ WHERE table_name = $1
131
+ ORDER BY ordinal_position
132
+ `, [table]);
65
133
  console.table(res.rows);
134
+ }
135
+ else if (DB_TYPE === 'sqlite') {
136
+ return new Promise((resolve) => {
137
+ conn.all(`PRAGMA table_info(\`${table}\`)`, (err, rows) => {
138
+ if (err) return console.error('Table not found or error:', err.message);
139
+ console.table(rows);
140
+ resolve();
141
+ });
142
+ });
143
+ }
144
+ else if (DB_TYPE === 'mongodb') {
145
+ const sample = await conn.collection(table).findOne();
146
+ if (!sample) {
147
+ console.log(`Collection "${table}" is empty.`);
148
+ return;
149
+ }
150
+ console.log(`📄 Sample document from "${table}":`);
151
+ console.dir(sample, { depth: null, colors: true });
152
+ }
153
+ };
154
+
155
+ export const countRows = async (table) => {
156
+ const conn = await getConnection();
157
+
158
+ if (DB_TYPE === 'mysql' || DB_TYPE === 'postgresql') {
159
+ const [res] = await conn.query(`SELECT COUNT(*) AS count FROM \`${table}\``);
160
+ console.log(`📊 Rows in "${table}": ${res[0].count}`);
161
+ }
162
+ else if (DB_TYPE === 'sqlite') {
163
+ return new Promise((resolve) => {
164
+ conn.get(`SELECT COUNT(*) AS count FROM \`${table}\``, (err, row) => {
165
+ if (err) return console.error(err.message);
166
+ console.log(`📊 Rows in "${table}": ${row.count}`);
167
+ resolve();
168
+ });
169
+ });
170
+ }
171
+ else if (DB_TYPE === 'mongodb') {
172
+ const count = await conn.collection(table).estimatedDocumentCount();
173
+ console.log(`📊 Documents in "${table}": ${count}`);
66
174
  }
67
175
  };
68
176
 
69
177
  export const truncateTable = async (table) => {
70
178
  const conn = await getConnection();
71
- await conn.query(`TRUNCATE TABLE \`${table}\` RESTART IDENTITY CASCADE`);
72
- console.log(`Truncated ${table}`);
179
+
180
+ if (DB_TYPE === 'mysql') {
181
+ await conn.query(`TRUNCATE TABLE \`${table}\``);
182
+ }
183
+ else if (DB_TYPE === 'postgresql') {
184
+ await conn.query(`TRUNCATE TABLE "${table}" RESTART IDENTITY CASCADE`);
185
+ }
186
+ else if (DB_TYPE === 'sqlite') {
187
+ await new Promise((resolve, reject) => {
188
+ conn.run(`DELETE FROM \`${table}\``, function(err) {
189
+ if (err) reject(err);
190
+ conn.run(`DELETE FROM sqlite_sequence WHERE name='${table}'`, () => resolve());
191
+ });
192
+ });
193
+ }
194
+ else if (DB_TYPE === 'mongodb') {
195
+ await conn.collection(table).deleteMany({});
196
+ }
197
+
198
+ console.log(` "${table}" truncated successfully`);
73
199
  };
74
200
 
75
201
  export const renameTable = async (oldName, newName) => {
76
202
  const conn = await getConnection();
77
- await conn.query(`RENAME TABLE \`${oldName}\` TO \`${newName}\``);
78
- console.log(`Renamed ${oldName} ${newName}`);
203
+
204
+ if (DB_TYPE === 'mysql') {
205
+ await conn.query(`ALTER TABLE \`${oldName}\` RENAME TO \`${newName}\``);
206
+ }
207
+ else if (DB_TYPE === 'postgresql') {
208
+ await conn.query(`ALTER TABLE "${oldName}" RENAME TO "${newName}"`);
209
+ }
210
+ else if (DB_TYPE === 'sqlite') {
211
+ await new Promise((resolve, reject) => {
212
+ conn.run(`ALTER TABLE \`${oldName}\` RENAME TO \`${newName}\``, err => {
213
+ err ? reject(err) : resolve();
214
+ });
215
+ });
216
+ }
217
+ else if (DB_TYPE === 'mongodb') {
218
+ await conn.collection(oldName).rename(newName);
219
+ }
220
+
221
+ console.log(` Renamed "${oldName}" → "${newName}"`);
79
222
  };
80
223
 
81
- export const backupDatabase = () => {
82
- const timestamp = new Date().toISOString().slice(0,19).replace(/:/g, '-');
83
- const backupFile = `backup-${DB_NAME}-${timestamp}.sql`;
224
+ export const checkTableExists = async (table) => {
225
+ const conn = await getConnection();
84
226
 
227
+ let exists = false;
85
228
  if (DB_TYPE === 'mysql') {
86
- execSync(`mysqldump -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > ${backupFile}`);
87
- } else if (DB_TYPE === 'postgresql') {
88
- execSync(`pg_dump -h ${DB_HOST} -U ${DB_USER} ${DB_NAME} > ${backupFile}`);
229
+ const [rows] = await conn.query(`SHOW TABLES LIKE '${table}'`);
230
+ exists = rows.length > 0;
231
+ }
232
+ else if (DB_TYPE === 'postgresql') {
233
+ const res = await conn.query(`SELECT to_regclass('public.${table}')`);
234
+ exists = res.rows[0].to_regclass !== null;
235
+ }
236
+ else if (DB_TYPE === 'sqlite') {
237
+ await new Promise((resolve) => {
238
+ conn.get(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`, (err, row) => {
239
+ exists = !!row;
240
+ resolve();
241
+ });
242
+ });
243
+ }
244
+ else if (DB_TYPE === 'mongodb') {
245
+ const collections = await conn.listCollections({ name: table }).toArray();
246
+ exists = collections.length > 0;
247
+ }
248
+
249
+ console.log(`Table/collection "${table}" ${exists ? ' exists' : ' does not exist'}`);
250
+ };
251
+
252
+ export const copyTable = async (source, destination) => {
253
+ const conn = await getConnection();
254
+
255
+ if (DB_TYPE === 'mysql' || DB_TYPE === 'postgresql') {
256
+ const sql = DB_TYPE === 'mysql'
257
+ ? `CREATE TABLE \`${destination}\` LIKE \`${source}\`; INSERT INTO \`${destination}\` SELECT * FROM \`${source}\`;`
258
+ : `CREATE TABLE "${destination}" AS TABLE "${source}" WITH DATA;`;
259
+ await conn.query(sql);
260
+ }
261
+ else if (DB_TYPE === 'sqlite') {
262
+ await new Promise((resolve, reject) => {
263
+ conn.run(`CREATE TABLE \`${destination}\` AS SELECT * FROM \`${source}\``, err => {
264
+ err ? reject(err) : resolve();
265
+ });
266
+ });
267
+ }
268
+
269
+ console.log(` Copied "${source}" → "${destination}"`);
270
+ };
271
+
272
+ export const backupDatabase = () => {
273
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
274
+ const file = `backup-${DB_NAME}-${timestamp}.sql`;
275
+
276
+ try {
277
+ if (DB_TYPE === 'mysql') {
278
+ execSync(`mysqldump -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > "${file}"`);
279
+ }
280
+ else if (DB_TYPE === 'postgresql') {
281
+ process.env.PGPASSWORD = DB_PASSWORD;
282
+ execSync(`pg_dump -h ${DB_HOST} -U ${DB_USER} ${DB_NAME} > "${file}"`);
283
+ }
284
+ else if (DB_TYPE === 'sqlite') {
285
+ fs.copyFileSync(`./${DB_NAME}.sqlite`, file.replace('.sql', '.sqlite'));
286
+ }
287
+
288
+ console.log(` Backup saved: ${file}`);
289
+ } catch (err) {
290
+ console.error(' Backup failed:', err.message);
89
291
  }
90
- console.log(`Backup saved: ${backupFile}`);
91
292
  };
92
293
 
93
294
  export const restoreDatabase = (file) => {
94
- if (!fs.existsSync(file)) return console.error('File not found');
295
+ if (!fs.existsSync(file)) {
296
+ console.error(' Backup file not found:', file);
297
+ return;
298
+ }
299
+
300
+ try {
301
+ if (DB_TYPE === 'mysql') {
302
+ execSync(`mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < "${file}"`);
303
+ }
304
+ else if (DB_TYPE === 'postgresql') {
305
+ process.env.PGPASSWORD = DB_PASSWORD;
306
+ execSync(`psql -h ${DB_HOST} -U ${DB_USER} -d ${DB_NAME} -f "${file}"`);
307
+ }
308
+ else if (DB_TYPE === 'sqlite') {
309
+ fs.copyFileSync(file, `./${DB_NAME}.sqlite`);
310
+ }
311
+
312
+ console.log(' Database restored from:', file);
313
+ } catch (err) {
314
+ console.error(' Restore failed:', err.message);
315
+ }
316
+ };
317
+
318
+ export const showDatabaseSize = async () => {
319
+ const conn = await getConnection();
95
320
 
96
321
  if (DB_TYPE === 'mysql') {
97
- execSync(`mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < ${file}`);
98
- } else if (DB_TYPE === 'postgresql') {
99
- execSync(`psql -h ${DB_HOST} -U ${DB_USER} -d ${DB_NAME} -f ${file}`);
100
- }
101
- console.log('Database restored');
102
- };
103
-
104
- // Add more functions similarly: exportTableToCsv, countRows, etc.
105
- // (You can expand this file further as needed)
106
-
107
- export const showDatabaseSize = () => {
108
- console.log(`Size estimation not implemented for ${DB_TYPE} yet.`);
109
- };
110
-
111
- // Placeholder for other commands...
112
- export const seedDatabase = (file) => console.log(`Seeding from ${file} not yet implemented`);
113
- export const listIndexes = () => console.log('Indexes list coming soon');
114
- export const vacuumDatabase = () => console.log('VACUUM executed');
115
- export const dropAllTables = () => console.log('All tables dropped (dangerous!)');
116
- export const copyTable = () => console.log('Table copied');
117
- export const exportTableToCsv = () => console.log('Exported to CSV');
118
- export const importCsvToTable = () => console.log('Imported CSV');
119
- export const countRows = () => console.log('Row count shown');
120
- export const analyzeTable = () => console.log('Table analyzed');
121
- export const reindexTable = () => console.log('Indexes rebuilt');
122
- export const showConnections = () => console.log('Active connections listed');
123
- export const killConnections = () => console.log('Connections killed');
124
- export const checkTableExists = () => console.log('Table existence checked');
322
+ const [res] = await conn.query(`
323
+ SELECT table_schema AS "Database",
324
+ ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS "Size (MB)"
325
+ FROM information_schema.TABLES
326
+ WHERE table_schema = '${DB_NAME}'
327
+ GROUP BY table_schema
328
+ `);
329
+ console.table(res);
330
+ }
331
+ else if (DB_TYPE === 'postgresql') {
332
+ const res = await conn.query(`SELECT pg_size_pretty(pg_database_size('${DB_NAME}')) AS size`);
333
+ console.log(`Database size: ${res.rows[0].size}`);
334
+ }
335
+ else if (DB_TYPE === 'sqlite') {
336
+ const stats = fs.statSync(`./${DB_NAME}.sqlite`);
337
+ console.log(`Database file size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
338
+ }
339
+ else if (DB_TYPE === 'mongodb') {
340
+ const stats = await conn.stats();
341
+ console.log(`Database size: ${(stats.dataSize / 1024 / 1024).toFixed(2)} MB`);
342
+ }
343
+ };
344
+
345
+ export const listIndexes = async (table) => {
346
+ const conn = await getConnection();
347
+
348
+ if (DB_TYPE === 'mysql') {
349
+ const [rows] = await conn.query(`SHOW INDEX FROM \`${table}\``);
350
+ console.table(rows);
351
+ }
352
+ else if (DB_TYPE === 'postgresql') {
353
+ const res = await conn.query(`
354
+ SELECT indexname, indexdef
355
+ FROM pg_indexes
356
+ WHERE tablename = $1
357
+ `, [table]);
358
+ console.table(res.rows);
359
+ }
360
+ };
361
+
362
+ export const vacuumDatabase = async () => {
363
+ const conn = await getConnection();
364
+
365
+ if (DB_TYPE === 'postgresql') {
366
+ await conn.query('VACUUM ANALYZE');
367
+ console.log(' VACUUM ANALYZE completed');
368
+ }
369
+ else if (DB_TYPE === 'sqlite') {
370
+ await new Promise((resolve) => {
371
+ conn.exec('VACUUM', () => {
372
+ console.log(' VACUUM completed');
373
+ resolve();
374
+ });
375
+ });
376
+ }
377
+ else {
378
+ console.log('VACUUM not supported for', DB_TYPE);
379
+ }
380
+ };
381
+
382
+ export const showConnections = async () => {
383
+ const conn = await getConnection();
384
+
385
+ if (DB_TYPE === 'postgresql') {
386
+ const res = await conn.query(`
387
+ SELECT count(*), state FROM pg_stat_activity
388
+ WHERE datname = '${DB_NAME}'
389
+ GROUP BY state
390
+ `);
391
+ console.table(res.rows);
392
+ }
393
+ else if (DB_TYPE === 'mysql') {
394
+ const [rows] = await conn.query('SHOW PROCESSLIST');
395
+ console.log(`Active connections: ${rows.length}`);
396
+ }
397
+ };
398
+
399
+ export const killConnections = async () => {
400
+ const conn = await getConnection();
401
+
402
+ if (DB_TYPE === 'postgresql') {
403
+ await conn.query(`
404
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
405
+ FROM pg_stat_activity
406
+ WHERE pg_stat_activity.datname = '${DB_NAME}'
407
+ AND pid <> pg_backend_pid()
408
+ `);
409
+ console.log(' All other connections terminated');
410
+ }
411
+ };
412
+
413
+ export const dropAllTables = async () => {
414
+ const conn = await getConnection();
415
+
416
+ if (DB_TYPE === 'mysql') {
417
+ await conn.query('SET FOREIGN_KEY_CHECKS = 0');
418
+ const [tables] = await conn.query('SHOW TABLES');
419
+ for (const t of tables) {
420
+ const tableName = Object.values(t)[0];
421
+ await conn.query(`DROP TABLE IF EXISTS \`${tableName}\``);
422
+ }
423
+ await conn.query('SET FOREIGN_KEY_CHECKS = 1');
424
+ }
425
+ else if (DB_TYPE === 'postgresql') {
426
+ const res = await conn.query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
427
+ for (const row of res.rows) {
428
+ await conn.query(`DROP TABLE IF EXISTS "${row.tablename}" CASCADE`);
429
+ }
430
+ }
431
+ else if (DB_TYPE === 'mongodb') {
432
+ const collections = await conn.listCollections().toArray();
433
+ for (const col of collections) {
434
+ await conn.collection(col.name).drop();
435
+ }
436
+ }
437
+
438
+ console.log('⚠️ All tables/collections dropped!');
439
+ };
440
+
441
+ export const exportTableToCsv = async (table, csvFile) => {
442
+ const conn = await getConnection();
443
+
444
+ const writableStream = createWriteStream(csvFile);
445
+
446
+ if (DB_TYPE === 'mysql' || DB_TYPE === 'postgresql') {
447
+ const [rows] = await conn.query(`SELECT * FROM \`${table}\``);
448
+ const stringifier = stringify({ header: true });
449
+ stringifier.pipe(writableStream);
450
+ rows.forEach(row => stringifier.write(row));
451
+ stringifier.end();
452
+ }
453
+ else if (DB_TYPE === 'sqlite') {
454
+ await new Promise((resolve) => {
455
+ conn.all(`SELECT * FROM \`${table}\``, (err, rows) => {
456
+ const stringifier = stringify({ header: true });
457
+ stringifier.pipe(writableStream);
458
+ rows.forEach(row => stringifier.write(row));
459
+ stringifier.end();
460
+ resolve();
461
+ });
462
+ });
463
+ }
464
+
465
+ console.log(` Exported "${table}" → ${csvFile}`);
466
+ };
467
+
468
+ export const importCsvToTable = async (csvFile, table) => {
469
+ if (!fs.existsSync(csvFile)) {
470
+ console.error('CSV file not found:', csvFile);
471
+ return;
472
+ }
473
+
474
+ const conn = await getConnection();
475
+ const results = [];
476
+
477
+ createReadStream(csvFile)
478
+ .pipe(csv())
479
+ .on('data', (data) => results.push(data))
480
+ .on('end', async () => {
481
+ if (results.length === 0) {
482
+ console.log('No data to import');
483
+ return;
484
+ }
485
+
486
+ const columns = Object.keys(results[0]).map(c => `\`${c}\``).join(', ');
487
+ const placeholders = Object.keys(results[0]).map(() => '?').join(', ');
488
+
489
+ for (const row of results) {
490
+ const values = Object.values(row);
491
+ await conn.query(`INSERT INTO \`${table}\` (${columns}) VALUES (${placeholders})`, values);
492
+ }
493
+
494
+ console.log(` Imported ${results.length} rows into "${table}"`);
495
+ });
496
+ };
497
+
498
+ export const seedDatabase = async (seedFile) => {
499
+ const fullPath = path.resolve(seedFile);
500
+
501
+ if (!fs.existsSync(fullPath)) {
502
+ console.error(' Seed file not found:', fullPath);
503
+ return;
504
+ }
505
+
506
+ try {
507
+ const seedModule = await import(`file://${fullPath}`);
508
+ if (typeof seedModule.default === 'function') {
509
+ await seedModule.default();
510
+ console.log(' Seed completed');
511
+ } else {
512
+ console.log('Seed file should export a default async function');
513
+ }
514
+ } catch (err) {
515
+ console.error(' Seed failed:', err.message);
516
+ }
517
+ };