mwalajs 1.1.17 → 1.1.18

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 HEKIMA MWALA
3
+ Copyright (c) 2026 HEKIMA MWALA
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/bin/mwala.mjs CHANGED
@@ -39,7 +39,24 @@ const __dirname = path.dirname(__filename);
39
39
  // ────────────────────────────────────────────────
40
40
 
41
41
  let imports = {};
42
- let setupMwalajs, createProject, dropAllTables, getDbConnection;
42
+ // let setupMwalajs, createProject, dropAllTables, getDbConnection;
43
+ let {
44
+ setupMwalajs,
45
+ createProject,
46
+ dropAllTables,
47
+ getDbConnection,
48
+ listTables,
49
+ createTable,
50
+ dropTable,
51
+ migrateAll,
52
+ rollbackLastMigration,
53
+ showDatabaseSize,
54
+ listIndexes,
55
+ analyzeTable,
56
+ vacuumDatabase,
57
+ showConnections,
58
+ killConnections,
59
+ } = imports;
43
60
 
44
61
  try {
45
62
  const [
@@ -48,6 +65,7 @@ try {
48
65
  setup,
49
66
  proj,
50
67
  dbUtilsRaw,
68
+ maintenance,
51
69
  ] = await Promise.all([
52
70
  import(pathToFileURL(path.join(__dirname, '../config/createdatabase.mjs')).href),
53
71
  import(pathToFileURL(path.join(__dirname, '../runMigrations.mjs')).href),
@@ -64,16 +82,31 @@ try {
64
82
  ...normalize(setup),
65
83
  ...normalize(proj),
66
84
  ...normalize(dbUtilsRaw),
85
+ ...normalize(maintenance), // ← important
67
86
  };
68
87
 
69
88
  // 🔥 IMPORTANT FIX
70
- ({
71
- setupMwalajs,
72
- createProject,
73
- dropAllTables,
74
- getDbConnection
75
- } = imports);
76
-
89
+ ({
90
+ setupMwalajs,
91
+ createProject,
92
+ dropAllTables,
93
+ getDbConnection,
94
+ listTables,
95
+ createTable,
96
+ dropTable,
97
+ migrateAll,
98
+ rollbackLastMigration,
99
+ // ongeza hizi zingine ukizihitaji baadaye
100
+ showDatabaseSize,
101
+ listIndexes,
102
+ analyzeTable,
103
+ vacuumDatabase,
104
+ showConnections,
105
+ killConnections,
106
+ // n.k.
107
+ } = imports);
108
+
109
+
77
110
  } catch (err) {
78
111
  error(`Failed to load required modules:\n${err.stack || err.message}`);
79
112
  process.exit(1);
@@ -596,38 +629,67 @@ case 'db:restore': {
596
629
  }
597
630
 
598
631
 
632
+ // case 'db:size':
633
+ // await runSafe(showDatabaseSize, 'Database size shown');
634
+ // break;
635
+
636
+ // case 'db:indexes':
637
+ // if (!args[1]) return error('Table name required');
638
+ // await runSafe(() => listIndexes(args[1]), `Indexes for ${args[1]}`);
639
+ // break;
640
+
641
+ // case 'db:analyze':
642
+ // if (!args[1]) return error('Table name required');
643
+ // await runSafe(() => analyzeTable(args[1]), `Table ${args[1]} analyzed`);
644
+ // break;
645
+
646
+ // case 'db:reindex':
647
+ // if (!args[1]) return error('Table name required');
648
+ // await runSafe(() => reindexTable(args[1]), `Table ${args[1]} reindexed`);
649
+ // break;
650
+
651
+ // case 'db:vacuum':
652
+ // await runSafe(vacuumDatabase, 'Database vacuumed');
653
+ // break;
654
+
655
+ // case 'db:connections':
656
+ // await runSafe(showConnections, 'Active connections shown');
657
+ // break;
658
+
659
+
599
660
  case 'db:size':
600
- await runSafe(showDatabaseSize, 'Database size shown');
601
- break;
661
+ await runSafe(() => imports.showDatabaseSize(), 'Database size shown');
662
+ break;
602
663
 
603
- case 'db:indexes':
604
- if (!args[1]) return error('Table name required');
605
- await runSafe(() => listIndexes(args[1]), `Indexes for ${args[1]}`);
606
- break;
664
+ case 'db:indexes':
665
+ if (!args[1]) return error('Table name required: mwala db:indexes <table>');
666
+ await runSafe(() => imports.listIndexes(args[1]), `Indexes for ${args[1]}`);
667
+ break;
607
668
 
608
- case 'db:analyze':
609
- if (!args[1]) return error('Table name required');
610
- await runSafe(() => analyzeTable(args[1]), `Table ${args[1]} analyzed`);
611
- break;
669
+ case 'db:analyze':
670
+ if (!args[1]) return error('Table name required');
671
+ await runSafe(() => imports.analyzeTable(args[1]), `Table ${args[1]} analyzed`);
672
+ break;
612
673
 
613
- case 'db:reindex':
614
- if (!args[1]) return error('Table name required');
615
- await runSafe(() => reindexTable(args[1]), `Table ${args[1]} reindexed`);
616
- break;
674
+ case 'db:vacuum':
675
+ await runSafe(() => imports.vacuumDatabase(), 'Database vacuumed');
676
+ break;
617
677
 
618
- case 'db:vacuum':
619
- await runSafe(vacuumDatabase, 'Database vacuumed');
620
- break;
678
+ case 'db:connections':
679
+ await runSafe(() => imports.showConnections(), 'Active connections shown');
680
+ break;
621
681
 
622
- case 'db:connections':
623
- await runSafe(showConnections, 'Active connections shown');
624
- break;
682
+ case 'db:kill-connections':
683
+ if (readlineSync.keyInYNStrict('⚠️ Kill ALL other database connections? (hatari!)')) {
684
+ await runSafe(() => imports.killConnections(), 'Other connections killed');
685
+ }
686
+ break;
625
687
 
626
- case 'db:kill-connections':
627
- if (readlineSync.keyInYNStrict('⚠️ Kill ALL other database connections?')) {
628
- await runSafe(killConnections, 'Other connections killed');
629
- }
630
- break;
688
+ // case 'db:kill-connections':
689
+ // if (readlineSync.keyInYNStrict('⚠️ Kill ALL other database connections?')) {
690
+ // await runSafe(killConnections, 'Other connections killed');
691
+ // }
692
+ // break;
631
693
 
632
694
  case 'db:drop-all-tables':
633
695
  if (readlineSync.keyInYNStrict('⚠️⚠️ THIS WILL DROP **ALL** TABLES! Continue?')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mwalajs",
3
- "version": "1.1.17",
3
+ "version": "1.1.18",
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/runMigrations.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // runMigrations.mjs
2
+
1
3
  import fs from 'fs';
2
4
  import path from 'path';
3
5
  import readline from 'readline';
@@ -134,6 +136,38 @@ export const listTables = async () => {
134
136
  }
135
137
  };
136
138
 
139
+
140
+ export const dropAllTables = async () => {
141
+ try {
142
+ const tables = await sequelize.getQueryInterface().showAllTables();
143
+
144
+ if (tables.length === 0) {
145
+ console.log('No tables exist in the database.');
146
+ return;
147
+ }
148
+
149
+ console.log(`Dropping ${tables.length} table(s): ${tables.join(', ')}`);
150
+
151
+ // Drop in reverse order to respect foreign key dependencies if any
152
+ for (const table of tables.reverse()) {
153
+ await sequelize.getQueryInterface().dropTable(table, { cascade: true });
154
+ console.log(` Dropped: ${table}`);
155
+ }
156
+
157
+ // Clear the migration tracking file so future migrates start clean
158
+ fs.writeFileSync(migrationLog, JSON.stringify([]));
159
+ console.log('Migration log cleared – ready for fresh migrations.');
160
+
161
+ } catch (error) {
162
+ console.error('Error while dropping all tables:', error.message);
163
+ if (error.stack) console.error(error.stack);
164
+ throw error; // let runSafe show the full error
165
+ }
166
+ };
167
+
168
+
169
+
170
+
137
171
  const askUser = (question) => {
138
172
  return new Promise((resolve) => {
139
173
  const rl = readline.createInterface({
@@ -146,3 +180,112 @@ const askUser = (question) => {
146
180
  });
147
181
  });
148
182
  };
183
+
184
+
185
+
186
+ // ────────────────────────────────────────────────
187
+ // MAINTENANCE TOOLS (MariaDB / MySQL compatible)
188
+ // ────────────────────────────────────────────────
189
+
190
+ export const showDatabaseSize = async () => {
191
+ const dbName = sequelize.getDatabaseName(); // au tumia sequelize.config.database kama haifanyi kazi
192
+ const [results] = await sequelize.query(`
193
+ SELECT
194
+ ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
195
+ FROM information_schema.tables
196
+ WHERE table_schema = '${dbName}'
197
+ `);
198
+
199
+ const size = results[0]?.size_mb || '0.00';
200
+ console.log(`Database size: ${size} MB`);
201
+ };
202
+
203
+ export const listIndexes = async (tableName) => {
204
+ const dbName = sequelize.getDatabaseName();
205
+ const [results] = await sequelize.query(`
206
+ SHOW INDEXES FROM \`${tableName}\`
207
+ `);
208
+
209
+ if (results.length === 0) {
210
+ console.log(`Hakuna indexes kwenye table "${tableName}".`);
211
+ return;
212
+ }
213
+
214
+ console.log(`Indexes kwenye "${tableName}":`);
215
+ results.forEach(row => {
216
+ const unique = row.Non_unique === 0 ? 'UNIQUE' : 'NON-UNIQUE';
217
+ console.log(` • ${row.Key_name.padEnd(35)} → ${unique} | Column: ${row.Column_name} | Type: ${row.Index_type}`);
218
+ });
219
+ };
220
+
221
+ export const analyzeTable = async (tableName) => {
222
+ await sequelize.query(`ANALYZE TABLE \`${tableName}\`;`);
223
+ console.log(`Table "${tableName}" ime-analyze (statistics zime-update).`);
224
+ };
225
+
226
+ export const vacuumDatabase = async () => {
227
+ // MySQL/MariaDB haina VACUUM kama PostgreSQL, lakini tunaweza optimize tables zote
228
+ const dbName = sequelize.getDatabaseName();
229
+ const [tables] = await sequelize.query(`
230
+ SELECT table_name
231
+ FROM information_schema.tables
232
+ WHERE table_schema = '${dbName}'
233
+ AND table_type = 'BASE TABLE'
234
+ `);
235
+
236
+ if (tables.length === 0) {
237
+ console.log('Hakuna tables za ku-optimize.');
238
+ return;
239
+ }
240
+
241
+ console.log(`Optimizing ${tables.length} table(s)...`);
242
+ for (const row of tables) {
243
+ const table = row.table_name;
244
+ await sequelize.query(`OPTIMIZE TABLE \`${table}\`;`);
245
+ console.log(` Optimized: ${table}`);
246
+ }
247
+ console.log('Optimization imekamilika (space reclaimed na stats updated).');
248
+ };
249
+
250
+ export const showConnections = async () => {
251
+ const [results] = await sequelize.query(`
252
+ SHOW PROCESSLIST
253
+ `);
254
+
255
+ const active = results.filter(row => row.Command !== 'Sleep' && row.Id !== 0); // exclude idle + our connection
256
+
257
+ if (active.length === 0) {
258
+ console.log('Hakuna connections zingine active (isipokuwa yako).');
259
+ return;
260
+ }
261
+
262
+ console.log(`Active connections (${active.length}):`);
263
+ active.forEach(r => {
264
+ const querySnippet = r.Info ? r.Info.substring(0, 60) + '...' : '(idle)';
265
+ console.log(` ID ${r.Id} | User: ${r.User} | Host: ${r.Host} | State: ${r.State} | Query: ${querySnippet}`);
266
+ });
267
+ };
268
+
269
+ export const killConnections = async () => {
270
+ const [results] = await sequelize.query(`
271
+ SHOW PROCESSLIST
272
+ `);
273
+
274
+ const toKill = results.filter(row => row.Id !== 0 && row.Command !== 'Sleep'); // exclude our connection + idle
275
+
276
+ if (toKill.length === 0) {
277
+ console.log('Hakuna connections za ku-kill.');
278
+ return;
279
+ }
280
+
281
+ console.log(`Killing ${toKill.length} connection(s)...`);
282
+ for (const row of toKill) {
283
+ try {
284
+ await sequelize.query(`KILL ${row.Id};`);
285
+ console.log(` Killed ID ${row.Id}`);
286
+ } catch (e) {
287
+ console.warn(` Failed to kill ${row.Id}: ${e.message}`);
288
+ }
289
+ }
290
+ console.log('Kill operation imekamilika.');
291
+ };