mwalajs 1.0.9 → 1.1.1
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 +5 -5
- package/package.json +3 -1
- package/utils/dbUtils.mjs +434 -67
package/bin/mwala.mjs
CHANGED
|
@@ -43,7 +43,7 @@ const {
|
|
|
43
43
|
if (!command || command === 'help' || command === 'h') {
|
|
44
44
|
console.log(`
|
|
45
45
|
╔══════════════════════════════════════════════════════════╗
|
|
46
|
-
║ MwalaJS CLI v1.
|
|
46
|
+
║ MwalaJS CLI v1.2.0 ║
|
|
47
47
|
╚══════════════════════════════════════════════════════════╝
|
|
48
48
|
|
|
49
49
|
General:
|
|
@@ -84,7 +84,7 @@ Database Commands:
|
|
|
84
84
|
mwala db:vacuum → Optimize (SQLite/PostgreSQL)
|
|
85
85
|
mwala db:connections → Show active connections
|
|
86
86
|
mwala db:kill-connections → Kill other connections (admin)
|
|
87
|
-
mwala db:drop-all-tables →
|
|
87
|
+
mwala db:drop-all-tables → DROP ALL TABLES!
|
|
88
88
|
|
|
89
89
|
mwala db:export <table> <file.csv> → Export to CSV
|
|
90
90
|
mwala db:import <file.csv> <table> → Import from CSV
|
|
@@ -94,7 +94,7 @@ Migrations:
|
|
|
94
94
|
mwala migrate all → Run all migrations
|
|
95
95
|
mwala rollback last → Rollback last migration
|
|
96
96
|
|
|
97
|
-
Enjoy building with MwalaJS!
|
|
97
|
+
Enjoy building with MwalaJS!
|
|
98
98
|
`);
|
|
99
99
|
process.exit(0);
|
|
100
100
|
}
|
|
@@ -297,7 +297,7 @@ switch (command) {
|
|
|
297
297
|
break;
|
|
298
298
|
|
|
299
299
|
case 'db:kill-connections':
|
|
300
|
-
if (readlineSync.keyInYN('
|
|
300
|
+
if (readlineSync.keyInYN(' Kill all other database connections?')) {
|
|
301
301
|
killConnections();
|
|
302
302
|
} else {
|
|
303
303
|
console.log('Cancelled.');
|
|
@@ -305,7 +305,7 @@ switch (command) {
|
|
|
305
305
|
break;
|
|
306
306
|
|
|
307
307
|
case 'db:drop-all-tables':
|
|
308
|
-
if (readlineSync.keyInYN('
|
|
308
|
+
if (readlineSync.keyInYN(' DROP ALL TABLES PERMANENTLY? Type YES to confirm:')) {
|
|
309
309
|
if (readlineSync.question('Type database name to confirm: ') === process.env.DB_NAME) {
|
|
310
310
|
dropAllTables();
|
|
311
311
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mwalajs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "MwalaJS Framework CLI Tool and Web Framework for Backend and Frontend Development.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "app.mjs",
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
"postinstall": "echo 'Thanks for installing MwalaJS Framework! '"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"csv-parser": "^3.2.0",
|
|
23
|
+
"csv-stringify": "^6.6.0",
|
|
22
24
|
"dotenv": "^16.4.7",
|
|
23
25
|
"ejs": "^3.1.10",
|
|
24
26
|
"express": "^4.21.2",
|
package/utils/dbUtils.mjs
CHANGED
|
@@ -1,97 +1,272 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
|
+
import { createWriteStream, createReadStream } from 'fs';
|
|
5
|
+
import csv from 'csv-parser';
|
|
6
|
+
import { stringify } from 'csv-stringify';
|
|
4
7
|
import dotenv from 'dotenv';
|
|
5
8
|
import mysql from 'mysql2/promise';
|
|
6
9
|
import { Client } from 'pg';
|
|
7
10
|
import { MongoClient } from 'mongodb';
|
|
8
11
|
import sqlite3 from 'sqlite3';
|
|
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 = 'localhost', 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;
|
|
14
19
|
|
|
15
|
-
|
|
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
|
+
};
|
|
35
|
+
|
|
36
|
+
// Global connection cache
|
|
37
|
+
let connection = null;
|
|
16
38
|
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
39
|
+
const getConnection = async () => {
|
|
40
|
+
if (!isDbConfigured()) {
|
|
41
|
+
suggestDbSetup();
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (connection) return connection;
|
|
19
46
|
|
|
20
47
|
try {
|
|
21
48
|
if (DB_TYPE === 'mysql') {
|
|
22
|
-
|
|
49
|
+
connection = await mysql.createConnection({
|
|
23
50
|
host: DB_HOST,
|
|
24
51
|
user: DB_USER,
|
|
25
52
|
password: DB_PASSWORD,
|
|
26
|
-
database: DB_NAME
|
|
53
|
+
database: DB_NAME,
|
|
54
|
+
port: DB_PORT || 3306
|
|
27
55
|
});
|
|
28
56
|
} else if (DB_TYPE === 'postgresql') {
|
|
29
|
-
|
|
57
|
+
connection = new Client({
|
|
30
58
|
host: DB_HOST,
|
|
31
59
|
user: DB_USER,
|
|
32
60
|
password: DB_PASSWORD,
|
|
33
|
-
database: DB_NAME
|
|
61
|
+
database: DB_NAME,
|
|
62
|
+
port: DB_PORT || 5432
|
|
34
63
|
});
|
|
35
|
-
await
|
|
64
|
+
await connection.connect();
|
|
36
65
|
} else if (DB_TYPE === 'sqlite') {
|
|
37
|
-
|
|
66
|
+
connection = new sqlite3.Database(`./${DB_NAME}.sqlite`);
|
|
67
|
+
connection.serialize();
|
|
38
68
|
} else if (DB_TYPE === 'mongodb') {
|
|
39
|
-
|
|
40
|
-
|
|
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);
|
|
41
74
|
}
|
|
42
75
|
} catch (err) {
|
|
43
|
-
console.error('
|
|
76
|
+
console.error(' Failed to connect to database:', err.message);
|
|
77
|
+
suggestDbSetup();
|
|
44
78
|
process.exit(1);
|
|
45
79
|
}
|
|
46
80
|
|
|
47
|
-
return
|
|
81
|
+
return connection;
|
|
48
82
|
};
|
|
49
83
|
|
|
84
|
+
// ====================== IMPLEMENTED COMMANDS ======================
|
|
85
|
+
|
|
50
86
|
export const listTables = async () => {
|
|
51
|
-
const
|
|
87
|
+
const conn = await getConnection();
|
|
88
|
+
|
|
52
89
|
if (DB_TYPE === 'mysql') {
|
|
53
|
-
const [rows] = await
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log('
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
const [rows] = await conn.query('SHOW TABLES');
|
|
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
|
+
});
|
|
61
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}`));
|
|
62
116
|
}
|
|
63
117
|
};
|
|
64
118
|
|
|
65
119
|
export const describeTable = async (table) => {
|
|
66
|
-
const
|
|
120
|
+
const conn = await getConnection();
|
|
121
|
+
|
|
67
122
|
if (DB_TYPE === 'mysql') {
|
|
68
|
-
const [rows] = await
|
|
123
|
+
const [rows] = await conn.query(`DESCRIBE \`${table}\``);
|
|
69
124
|
console.table(rows);
|
|
70
|
-
}
|
|
71
|
-
|
|
125
|
+
}
|
|
126
|
+
else if (DB_TYPE === 'postgresql') {
|
|
127
|
+
const res = await conn.query(`
|
|
72
128
|
SELECT column_name, data_type, is_nullable, column_default
|
|
73
|
-
FROM information_schema.columns
|
|
129
|
+
FROM information_schema.columns
|
|
130
|
+
WHERE table_name = $1
|
|
131
|
+
ORDER BY ordinal_position
|
|
74
132
|
`, [table]);
|
|
75
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 });
|
|
76
152
|
}
|
|
77
153
|
};
|
|
78
154
|
|
|
79
155
|
export const countRows = async (table) => {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
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}`);
|
|
174
|
+
}
|
|
83
175
|
};
|
|
84
176
|
|
|
85
177
|
export const truncateTable = async (table) => {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
178
|
+
const conn = await getConnection();
|
|
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`);
|
|
89
199
|
};
|
|
90
200
|
|
|
91
201
|
export const renameTable = async (oldName, newName) => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
202
|
+
const conn = await getConnection();
|
|
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}"`);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const checkTableExists = async (table) => {
|
|
225
|
+
const conn = await getConnection();
|
|
226
|
+
|
|
227
|
+
let exists = false;
|
|
228
|
+
if (DB_TYPE === 'mysql') {
|
|
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}"`);
|
|
95
270
|
};
|
|
96
271
|
|
|
97
272
|
export const backupDatabase = () => {
|
|
@@ -100,51 +275,243 @@ export const backupDatabase = () => {
|
|
|
100
275
|
|
|
101
276
|
try {
|
|
102
277
|
if (DB_TYPE === 'mysql') {
|
|
103
|
-
execSync(`mysqldump -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > ${file}`);
|
|
104
|
-
}
|
|
278
|
+
execSync(`mysqldump -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > "${file}"`);
|
|
279
|
+
}
|
|
280
|
+
else if (DB_TYPE === 'postgresql') {
|
|
105
281
|
process.env.PGPASSWORD = DB_PASSWORD;
|
|
106
|
-
execSync(`pg_dump -h ${DB_HOST} -U ${DB_USER} ${DB_NAME} > ${file}`);
|
|
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'));
|
|
107
286
|
}
|
|
108
|
-
|
|
287
|
+
|
|
288
|
+
console.log(` Backup saved: ${file}`);
|
|
109
289
|
} catch (err) {
|
|
110
|
-
console.error('Backup failed:', err.message);
|
|
290
|
+
console.error(' Backup failed:', err.message);
|
|
111
291
|
}
|
|
112
292
|
};
|
|
113
293
|
|
|
114
294
|
export const restoreDatabase = (file) => {
|
|
115
295
|
if (!fs.existsSync(file)) {
|
|
116
|
-
console.error('Backup file not found');
|
|
296
|
+
console.error(' Backup file not found:', file);
|
|
117
297
|
return;
|
|
118
298
|
}
|
|
299
|
+
|
|
119
300
|
try {
|
|
120
301
|
if (DB_TYPE === 'mysql') {
|
|
121
|
-
execSync(`mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < ${file}`);
|
|
122
|
-
}
|
|
302
|
+
execSync(`mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < "${file}"`);
|
|
303
|
+
}
|
|
304
|
+
else if (DB_TYPE === 'postgresql') {
|
|
123
305
|
process.env.PGPASSWORD = DB_PASSWORD;
|
|
124
|
-
execSync(`psql -h ${DB_HOST} -U ${DB_USER} -d ${DB_NAME} -f ${file}`);
|
|
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`);
|
|
125
310
|
}
|
|
126
|
-
|
|
311
|
+
|
|
312
|
+
console.log(' Database restored from:', file);
|
|
127
313
|
} catch (err) {
|
|
128
|
-
console.error('Restore failed:', err.message);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export const showDatabaseSize = () =>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
314
|
+
console.error(' Restore failed:', err.message);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export const showDatabaseSize = async () => {
|
|
319
|
+
const conn = await getConnection();
|
|
320
|
+
|
|
321
|
+
if (DB_TYPE === 'mysql') {
|
|
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
|
+
});
|
|
145
462
|
});
|
|
146
|
-
} else {
|
|
147
|
-
console.error('Seed file not found');
|
|
148
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
|
+
});
|
|
149
496
|
};
|
|
150
|
-
|
|
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
|
+
};
|