mysql-migration 1.2.5 → 1.4.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.
@@ -1,26 +1,33 @@
1
1
  "use strict";
2
- //---------------------------------------
2
+
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
- //---------------------------------------
5
+
6
6
  const currentPath = process.cwd();
7
- const config = require(`${currentPath}/migrations/mysql-migration.config.json`);
8
- //---------------------------------------
7
+ const configPath = path.join(currentPath, "migrations", "mysql-migration.config.json");
8
+
9
+ const { loadConfig, getDatabaseNames, isValidDatabase } = require("../utils/config");
10
+
11
+ /**
12
+ * Convert migration files within a database directory from CommonJS to ESM format.
13
+ * @param {string} dbName - Target database identifier.
14
+ * @returns {void}
15
+ */
9
16
  function convert_to_esm(dbName) {
10
- //---------------------------------------
11
- const databases = Object.keys(config.databases);
12
- //---------------------------------------
13
- if (!databases.includes(dbName)) {
14
- console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}" can be: ${databases.join(", ")}.`);
17
+ const config = loadConfig(configPath);
18
+ const databases = getDatabaseNames(config);
19
+
20
+ if (!isValidDatabase(dbName, config)) {
21
+ console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}". Available databases: ${databases.join(", ")}.`);
15
22
  process.exit(1);
16
23
  }
17
- //---------------------------------------
18
- const migrationsDir = `${currentPath}/migrations/${dbName}_db`;
24
+
25
+ const migrationsDir = path.join(currentPath, "migrations", `${dbName}_db`);
19
26
  if (!fs.existsSync(migrationsDir)) {
20
27
  console.error("\x1b[31m%s\x1b[0m", `Error: Migrations directory for "${dbName}" not found.`);
21
28
  process.exit(1);
22
29
  }
23
- //---------------------------------------
30
+
24
31
  const files = fs.readdirSync(migrationsDir);
25
32
  let convertedCount = 0;
26
33
 
@@ -45,5 +52,5 @@ function convert_to_esm(dbName) {
45
52
  console.log("\x1b[32m%s\x1b[0m", `\nSuccessfully converted ${convertedCount} files to ESM.`);
46
53
  }
47
54
  }
48
- //---------------------------------------
55
+
49
56
  module.exports = convert_to_esm;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+
5
+ /**
6
+ * Load and parse configuration file
7
+ * @param {string} configPath - Path to config file
8
+ * @returns {{databases: Record<string, import('mysql2').ConnectionOptions>}}
9
+ * @throws {Error} If config file not found or invalid
10
+ */
11
+ function loadConfig(configPath) {
12
+ if (!fs.existsSync(configPath)) {
13
+ throw new Error('Config file not found. Run "mysql-migration init" first.');
14
+ }
15
+
16
+ const rawConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
17
+
18
+ // Validate config structure
19
+ if (!rawConfig.databases || typeof rawConfig.databases !== "object") {
20
+ throw new Error('Invalid config file: missing or invalid "databases" object');
21
+ }
22
+
23
+ return rawConfig;
24
+ }
25
+
26
+ /**
27
+ * Get database configuration
28
+ * @param {string} dbName - Database name
29
+ * @param {{databases: Record<string, any>}} config - Raw config object
30
+ * @returns {import('mysql2').ConnectionOptions}
31
+ */
32
+ function getDatabaseConfig(dbName, config) {
33
+ const dbConfig = config.databases[dbName];
34
+
35
+ if (!dbConfig) {
36
+ throw new Error(`Database "${dbName}" not found in configuration`);
37
+ }
38
+
39
+ return { ...dbConfig };
40
+ }
41
+
42
+ /**
43
+ * Get all database names from config
44
+ * @param {{databases: Record<string, any>}} config - Raw config object
45
+ * @returns {string[]}
46
+ */
47
+ function getDatabaseNames(config) {
48
+ return Object.keys(config.databases);
49
+ }
50
+
51
+ /**
52
+ * Validate database name exists in config
53
+ * @param {string} dbName - Database name to validate
54
+ * @param {{databases: Record<string, any>}} config - Raw config object
55
+ * @returns {boolean}
56
+ */
57
+ function isValidDatabase(dbName, config) {
58
+ return dbName in config.databases;
59
+ }
60
+
61
+ module.exports = {
62
+ loadConfig,
63
+ getDatabaseConfig,
64
+ getDatabaseNames,
65
+ isValidDatabase,
66
+ };
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+
3
+ const mysql = require("mysql2");
4
+
5
+ /**
6
+ * Connection manager for handling database connections with proper cleanup
7
+ */
8
+ class ConnectionManager {
9
+ constructor() {
10
+ /** @type {Map<string, import('mysql2').Connection>} */
11
+ this.connections = new Map();
12
+ }
13
+
14
+ /**
15
+ * Create a new database connection
16
+ * @param {string} name - Connection name/identifier
17
+ * @param {import('mysql2').ConnectionOptions} config - Database configuration
18
+ * @returns {Promise<import('mysql2').Connection>}
19
+ */
20
+ async createConnection(name, config) {
21
+ if (this.connections.has(name)) {
22
+ throw new Error(`Connection "${name}" already exists`);
23
+ }
24
+
25
+ const connection = mysql.createConnection(config);
26
+ await connection.promise().connect();
27
+ this.connections.set(name, connection);
28
+ return connection;
29
+ }
30
+
31
+ /**
32
+ * Get an existing connection
33
+ * @param {string} name - Connection name/identifier
34
+ * @returns {import('mysql2').Connection|undefined}
35
+ */
36
+ getConnection(name) {
37
+ return this.connections.get(name);
38
+ }
39
+
40
+ /**
41
+ * Close a specific connection
42
+ * @param {string} name - Connection name/identifier
43
+ * @returns {Promise<void>}
44
+ */
45
+ async closeConnection(name) {
46
+ const connection = this.connections.get(name);
47
+ if (connection) {
48
+ await connection.promise().end();
49
+ this.connections.delete(name);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Close all connections
55
+ * @returns {Promise<void>}
56
+ */
57
+ async closeAll() {
58
+ const closePromises = [];
59
+ for (const [name, connection] of this.connections.entries()) {
60
+ closePromises.push(
61
+ connection.promise().end().catch((err) => {
62
+ console.warn(`Warning: Error closing connection "${name}": ${err.message}`);
63
+ })
64
+ );
65
+ }
66
+ await Promise.all(closePromises);
67
+ this.connections.clear();
68
+ }
69
+
70
+ /**
71
+ * Execute a callback with automatic connection cleanup
72
+ * @template T
73
+ * @param {string} name - Connection name/identifier
74
+ * @param {import('mysql2').ConnectionOptions} config - Database configuration
75
+ * @param {(connection: import('mysql2').Connection) => Promise<T>} callback - Function to execute with the connection
76
+ * @returns {Promise<T>}
77
+ */
78
+ async withConnection(name, config, callback) {
79
+ const connection = await this.createConnection(name, config);
80
+ try {
81
+ return await callback(connection);
82
+ } finally {
83
+ await this.closeConnection(name);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Execute a callback with automatic cleanup of all connections
89
+ * @template T
90
+ * @param {() => Promise<T>} callback - Function to execute
91
+ * @returns {Promise<T>}
92
+ */
93
+ async withCleanup(callback) {
94
+ try {
95
+ return await callback();
96
+ } finally {
97
+ await this.closeAll();
98
+ }
99
+ }
100
+ }
101
+
102
+ module.exports = ConnectionManager;
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Check if the migrations table exists in the current database.
3
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
4
+ * @returns {Promise<boolean>} True if the table exists, false if it needs to be created.
5
+ */
1
6
  async function checkTableMigrations(connection) {
2
7
  try {
3
8
  await connection.promise().query("SELECT `id` FROM `migrations` LIMIT 1;");
@@ -7,7 +12,12 @@ async function checkTableMigrations(connection) {
7
12
  throw error;
8
13
  }
9
14
  }
10
- //---------------------------------------
15
+
16
+ /**
17
+ * Create the migrations table.
18
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
19
+ * @returns {Promise<boolean>} Resolves true when the table has been created.
20
+ */
11
21
  async function createTableMigrations(connection) {
12
22
  const query =
13
23
  "CREATE TABLE `migrations` (\
@@ -19,7 +29,13 @@ async function createTableMigrations(connection) {
19
29
  await connection.promise().query(query);
20
30
  return true;
21
31
  }
22
- //---------------------------------------
32
+
33
+ /**
34
+ * Retrieve migrations optionally filtered by batch.
35
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
36
+ * @param {number|null} batch - Minimum batch number (exclusive) to filter results.
37
+ * @returns {Promise<Array<{migration: string}>>} List of migrations.
38
+ */
23
39
  async function getAllMigrations(connection, batch = null) {
24
40
  let query = "SELECT `migration` FROM `migrations`";
25
41
  const params = [];
@@ -30,31 +46,98 @@ async function getAllMigrations(connection, batch = null) {
30
46
  const [results] = await connection.promise().query(`${query};`, params);
31
47
  return Array.isArray(results) ? results : [];
32
48
  }
33
- //---------------------------------------
49
+
50
+ /**
51
+ * Get the latest batch number applied to the database.
52
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
53
+ * @returns {Promise<number>} Highest batch number or 0 if none exist.
54
+ */
34
55
  async function getCurrentBatch(connection) {
35
56
  const [results] = await connection
36
57
  .promise()
37
58
  .query("SELECT `batch` FROM `migrations` ORDER BY `batch` DESC LIMIT 1;");
38
59
  return Array.isArray(results) && results.length > 0 ? results[0].batch : 0;
39
60
  }
40
- //---------------------------------------
61
+
62
+ /**
63
+ * Record a migration as executed.
64
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
65
+ * @param {string} migration - Migration identifier.
66
+ * @param {number} batch - Batch number the migration belongs to.
67
+ * @returns {Promise<boolean>} Resolves true after insertion.
68
+ */
41
69
  async function insertMigration(connection, migration, batch) {
42
70
  const query = "INSERT INTO `migrations` (`migration`, `batch`) VALUES (?, ?);";
43
71
  await connection.promise().query(query, [migration, batch]);
44
72
  return true;
45
73
  }
46
- //---------------------------------------
74
+
75
+ /**
76
+ * Remove a migration entry above the provided batch.
77
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
78
+ * @param {string} migration - Migration identifier to delete.
79
+ * @param {number} batch - Batch threshold; deletes records with batch greater than this value.
80
+ * @returns {Promise<boolean>} Resolves true after deletion.
81
+ */
47
82
  async function deleteMigration(connection, migration, batch) {
48
83
  const query = "DELETE FROM `migrations` WHERE `migration` = ? AND `batch` > ?;";
49
84
  await connection.promise().query(query, [migration, batch]);
50
85
  return true;
51
86
  }
52
- //---------------------------------------
87
+
88
+ /**
89
+ * Retrieve all stored batches with their migrations.
90
+ * @param {import('mysql2').Connection} connection - Active MySQL connection.
91
+ * @returns {Promise<Array<{batch: number, migration: string}>>} List of batches and their migrations.
92
+ */
53
93
  async function getAllBatches(connection) {
54
94
  const [results] = await connection.promise().query("SELECT `batch`, `migration` FROM `migrations`;");
55
95
  return Array.isArray(results) ? results : [];
56
96
  }
57
- //---------------------------------------
97
+
98
+ /**
99
+ * Begin a transaction
100
+ * @param {import('mysql2').Connection} connection
101
+ */
102
+ async function beginTransaction(connection) {
103
+ await connection.promise().query("START TRANSACTION");
104
+ }
105
+
106
+ /**
107
+ * Commit a transaction
108
+ * @param {import('mysql2').Connection} connection
109
+ */
110
+ async function commitTransaction(connection) {
111
+ await connection.promise().query("COMMIT");
112
+ }
113
+
114
+ /**
115
+ * Rollback a transaction
116
+ * @param {import('mysql2').Connection} connection
117
+ */
118
+ async function rollbackTransaction(connection) {
119
+ await connection.promise().query("ROLLBACK");
120
+ }
121
+
122
+ /**
123
+ * Execute a callback within a transaction
124
+ * @template T
125
+ * @param {import('mysql2').Connection} connection
126
+ * @param {(connection: import('mysql2').Connection) => Promise<T>} callback
127
+ * @returns {Promise<T>}
128
+ */
129
+ async function withTransaction(connection, callback) {
130
+ await beginTransaction(connection);
131
+ try {
132
+ const result = await callback(connection);
133
+ await commitTransaction(connection);
134
+ return result;
135
+ } catch (error) {
136
+ await rollbackTransaction(connection);
137
+ throw error;
138
+ }
139
+ }
140
+
58
141
  module.exports = {
59
142
  checkTableMigrations,
60
143
  createTableMigrations,
@@ -63,4 +146,8 @@ module.exports = {
63
146
  insertMigration,
64
147
  deleteMigration,
65
148
  getAllBatches,
149
+ beginTransaction,
150
+ commitTransaction,
151
+ rollbackTransaction,
152
+ withTransaction,
66
153
  };
@@ -1,4 +1,3 @@
1
- const path = require("path");
2
1
  const { pathToFileURL } = require("url");
3
2
 
4
3
  /**
package/.prettierrc.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "semi": true,
3
- "singleQuote": false,
4
- "tabWidth": 4,
5
- "useTabs": false,
6
- "trailingComma": "es5",
7
- "printWidth": 120,
8
- "arrowParens": "always",
9
- "endOfLine": "lf"
10
- }