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.
- package/README.md +90 -11
- package/index.d.ts +92 -0
- package/index.js +17 -12
- package/package.json +26 -7
- package/src/commands/back.js +69 -60
- package/src/commands/batch.js +47 -42
- package/src/commands/create.js +68 -51
- package/src/commands/init.js +31 -24
- package/src/commands/run.js +203 -131
- package/src/commands/to-cjs.js +20 -13
- package/src/commands/to-esm.js +20 -13
- package/src/utils/config.js +66 -0
- package/src/utils/connectionManager.js +102 -0
- package/src/utils/functions.js +94 -7
- package/src/utils/moduleLoader.js +0 -1
- package/.prettierrc.json +0 -10
package/src/commands/to-esm.js
CHANGED
|
@@ -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
|
|
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 =
|
|
12
|
-
|
|
13
|
-
if (!
|
|
14
|
-
console.error("\x1b[31m%s\x1b[0m", `Error: Invalid database name "${dbName}"
|
|
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 =
|
|
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;
|
package/src/utils/functions.js
CHANGED
|
@@ -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
|
};
|