mysql-memory-server 1.6.0 → 1.7.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/README.md +38 -16
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +56 -0
- package/dist/src/constants.d.ts +10 -3
- package/dist/src/constants.js +126 -3
- package/dist/src/index.js +40 -31
- package/dist/src/libraries/Downloader.js +25 -16
- package/dist/src/libraries/Executor.d.ts +2 -0
- package/dist/src/libraries/Executor.js +75 -31
- package/dist/src/libraries/FileLock.d.ts +1 -0
- package/dist/src/libraries/FileLock.js +69 -13
- package/dist/src/libraries/Logger.js +2 -6
- package/dist/src/libraries/Version.js +17 -7
- package/dist/types/index.d.ts +19 -6
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MySQL Memory Server
|
|
2
2
|
|
|
3
|
-
This package allows you to create ephemeral MySQL databases from JavaScript and/or TypeScript code, great for testing. When creating a new database, if the version selected is not installed on the system, the binary is downloaded from MySQL's CDN (cdn.mysql.com)
|
|
3
|
+
This package allows you to create ephemeral MySQL databases from JavaScript and/or TypeScript code and also the CLI, great for testing, CI, and learning MySQL. When creating a new database, if the version selected is not installed on the system, the binary is downloaded from MySQL's CDN (cdn.mysql.com)
|
|
4
4
|
|
|
5
5
|
You can run multiple MySQL databases with this package at the same time. Each database will use a random free port. The databases will automatically shutdown when the JS runtime process exits. A `stop()` method is also provided to stop each database instance.
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ Requirements for Linux:
|
|
|
30
30
|
- If using the system installed MySQL server: 8.0.20 and newer
|
|
31
31
|
- If not using the system installed MySQL server: 8.0.39, 8.0.40, 8.1.0, 8.2.0, 8.3.0, 8.4.2, 8.4.3, 9.0.1, 9.1.0
|
|
32
32
|
|
|
33
|
-
## Usage
|
|
33
|
+
## Example Usage - Application Code
|
|
34
34
|
|
|
35
35
|
This package supports both ESM and CJS so you can use import or require.
|
|
36
36
|
|
|
@@ -45,7 +45,7 @@ const db = await createDB()
|
|
|
45
45
|
|
|
46
46
|
//Create a new database with custom options set
|
|
47
47
|
const db = await createDB({
|
|
48
|
-
// see Options for the options you can use in this object and their default values
|
|
48
|
+
// see Options below for the options you can use in this object and their default values
|
|
49
49
|
// for example:
|
|
50
50
|
version: '8.4.x'
|
|
51
51
|
})
|
|
@@ -73,6 +73,14 @@ await db.stop()
|
|
|
73
73
|
MySQL database initialization can take some time. If you run into a "Timeout exceeded" error with your tests, the timeout should be extended.
|
|
74
74
|
If using Jest, information about how to do this can be found here: https://jestjs.io/docs/jest-object#jestsettimeouttimeout
|
|
75
75
|
|
|
76
|
+
## Example Usage - CLI
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
# Options are added by doing --{optionName} {optionValue}
|
|
80
|
+
# See Options below for the options you can use with this package
|
|
81
|
+
npx mysql-memory-server@latest --version 8.4.x
|
|
82
|
+
```
|
|
83
|
+
|
|
76
84
|
## Documentation
|
|
77
85
|
|
|
78
86
|
##### `createDB(options: ServerOptions): Promise<MySQLDB>`
|
|
@@ -86,10 +94,14 @@ The port that MySQLX is listening on
|
|
|
86
94
|
The database that was created on database initialization
|
|
87
95
|
- `username: string`
|
|
88
96
|
The name of the user to use to login to the database
|
|
97
|
+
- `socket: string`
|
|
98
|
+
If on Windows, this is the name of the named pipe that MySQL is listening on. If not on Windows, this is the path to the socket that MySQL is listening on.
|
|
99
|
+
- `xSocket: string`
|
|
100
|
+
If on Windows, this is the name of the named pipe that the MySQL X Plugin is listening on. If not on Windows, this is the path that the MySQL X Plugin is listening on.
|
|
89
101
|
- `stop: () => Promise<void>`
|
|
90
102
|
The method to stop the database. The returned promise resolves when the database has successfully stopped.
|
|
91
103
|
|
|
92
|
-
|
|
104
|
+
#### Options:
|
|
93
105
|
- `version: string`
|
|
94
106
|
|
|
95
107
|
Required: No
|
|
@@ -104,7 +116,7 @@ If left undefined:
|
|
|
104
116
|
|
|
105
117
|
If defined:
|
|
106
118
|
- If the version is 8.0.19 or older, an error will be thrown as this package does not currently support those versions of MySQL.
|
|
107
|
-
- If MySQL is installed on the system, the installed version will be used. Otherwise the selected version will be downloaded from the MySQL CDN as long as it can be found in the `versions.json` file. If it cannot be found in that file, an error will be thrown.
|
|
119
|
+
- If the desired version of MySQL is installed on the system, the installed version will be used. Otherwise the selected version will be downloaded from the MySQL CDN as long as it can be found in the `versions.json` file. If it cannot be found in that file, an error will be thrown.
|
|
108
120
|
|
|
109
121
|
- `dbName: string`
|
|
110
122
|
|
|
@@ -202,35 +214,45 @@ Required: No
|
|
|
202
214
|
|
|
203
215
|
Default: ""
|
|
204
216
|
|
|
205
|
-
Description: A string with MySQL queries to run before the database starts to accept connections. This option can be used for things like initialising tables without having to first connect to the database to do that.The queries in the string get executed after ```mysql-memory-server```'s queries run. Uses the ```--init-file``` MySQL server option under the hood. Learn more at the [--init-file MySQL Documentation](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_init_file)
|
|
217
|
+
Description: A string with MySQL queries to run before the database starts to accept connections. This option can be used for things like initialising tables without having to first connect to the database to do that. The queries in the string get executed after ```mysql-memory-server```'s queries run. Uses the ```--init-file``` MySQL server option under the hood. Learn more at the [--init-file MySQL Documentation](https://dev.mysql.com/doc/refman/8.4/en/server-system-variables.html#sysvar_init_file)
|
|
206
218
|
|
|
207
219
|
The internal queries that are ran before the queries in ```initSQLString``` are renaming the user from ```root``` to the username specified in the ```username``` option if it's set, and creating a database with the name from the ```dbName``` option.
|
|
208
220
|
|
|
209
221
|
***
|
|
210
222
|
### :warning: Internal Options :warning:
|
|
211
223
|
|
|
212
|
-
The following options are only meant for internal
|
|
224
|
+
The following options are only meant for internal use (such as CI or the internals for running this package via the CLI). Their behaviour may change or they may get removed between major/minor/patch versions and they are not to be considered stable. The options below will not follow Semantic Versioning so it is advised to not use them.
|
|
213
225
|
|
|
214
|
-
- `
|
|
215
|
-
|
|
216
|
-
Required: No
|
|
226
|
+
- `_DO_NOT_USE_deleteDBAfterStopped: boolean`
|
|
217
227
|
|
|
218
228
|
Default: true
|
|
219
229
|
|
|
220
230
|
Description: Changes whether or not the database will be deleted after it has been stopped. If set to `true`, the database WILL be deleted after it has been stopped.
|
|
221
231
|
|
|
222
|
-
- `
|
|
223
|
-
|
|
224
|
-
Required: No
|
|
232
|
+
- `_DO_NOT_USE_dbPath: string`
|
|
225
233
|
|
|
226
234
|
Default: `TMPDIR/mysqlmsn/dbs/UUID` (replacing TMPDIR with the OS temp directory and UUID with a UUIDv4 without seperating dashes).
|
|
227
235
|
|
|
228
236
|
Description: The folder to store database-related data in
|
|
229
237
|
|
|
230
|
-
- `
|
|
231
|
-
|
|
232
|
-
Required: No
|
|
238
|
+
- `_DO_NOT_USE_binaryDirectoryPath: string`
|
|
233
239
|
|
|
234
240
|
Default: `TMPDIR/mysqlmsn/binaries` (replacing TMPDIR with the OS temp directory)
|
|
235
241
|
|
|
236
242
|
Description: The folder to store the MySQL binaries when they are downloaded from the CDN.
|
|
243
|
+
|
|
244
|
+
- `_DO_NOT_USE_beforeSignalCleanupMessage: string`
|
|
245
|
+
|
|
246
|
+
Required: No
|
|
247
|
+
|
|
248
|
+
Default: undefined
|
|
249
|
+
|
|
250
|
+
Description: The message to get displayed in the console before the cleanup that happens when the Node.js process is stopped without the ```stop()``` method being called first.
|
|
251
|
+
|
|
252
|
+
- `_DO_NOT_USE_afterSignalCleanupMessage: string`
|
|
253
|
+
|
|
254
|
+
Required: No
|
|
255
|
+
|
|
256
|
+
Default: undefined
|
|
257
|
+
|
|
258
|
+
Description: The message to get displayed in the console after the cleanup that happens when the Node.js process is stopped without the ```stop()``` method being called first.
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
async function main() {
|
|
7
|
+
const definedOptions = process.argv.filter((option) => option.startsWith('--'));
|
|
8
|
+
const options = {
|
|
9
|
+
_DO_NOT_USE_beforeSignalCleanupMessage: '\nShutting down the ephemeral MySQL database and cleaning all related files...',
|
|
10
|
+
_DO_NOT_USE_afterSignalCleanupMessage: 'Shutdown and cleanup is complete.'
|
|
11
|
+
};
|
|
12
|
+
for (const opt of definedOptions) {
|
|
13
|
+
const index = process.argv.indexOf(opt);
|
|
14
|
+
const optionValue = process.argv[index + 1];
|
|
15
|
+
if (optionValue === undefined) {
|
|
16
|
+
throw `Option ${opt} must have a value.`;
|
|
17
|
+
}
|
|
18
|
+
const optionName = opt.slice(2);
|
|
19
|
+
const optionType = constants_1.OPTION_TYPE_CHECKS[optionName].definedType;
|
|
20
|
+
//Try to convert the options to their correct types.
|
|
21
|
+
//We do not need to do any proper type validation here as the library will make sure everything is correct.
|
|
22
|
+
//Like for example, if a string is passed to a number option, it'll be converted to NaN here, but the library
|
|
23
|
+
//will throw an error for it not being an actual number.
|
|
24
|
+
if (optionType === 'boolean') {
|
|
25
|
+
if (optionValue === 'true') {
|
|
26
|
+
options[optionName] = true;
|
|
27
|
+
}
|
|
28
|
+
else if (optionValue === 'false') {
|
|
29
|
+
options[optionName] = false;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
options[optionName] = optionValue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (optionType === 'number') {
|
|
36
|
+
options[optionName] = parseInt(optionValue);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
options[opt.slice(2)] = optionValue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.log('Creating ephemeral MySQL database...');
|
|
43
|
+
const db = await (0, index_1.createDB)(options);
|
|
44
|
+
console.log(`A MySQL database has been successfully created with the following parameters:\n\nUsername: ${db.username} \nDatabase Name: ${db.dbName} \nPort: ${db.port} \nX Plugin Port: ${db.xPort} \nSocket: ${db.socket} \nX Plugin Socket: ${db.xSocket}\n`);
|
|
45
|
+
if (process.platform === 'win32') {
|
|
46
|
+
//The connection information logs will be different for Windows compared to other platforms.
|
|
47
|
+
//Windows uses mysqlsh instead of mysql to invoke the client shell, needs a --sql flag to be put into SQL mode, and also does not have a protocol flag.
|
|
48
|
+
//Also according to https://bugs.mysql.com/bug.php?id=106852, you cannot log into a MySQL database with a named pipe for the first connection so a socket connection suggestion
|
|
49
|
+
//should only be displayed for non-Windows platforms.
|
|
50
|
+
console.log(`If you want to use the MySQL CLI client to connect to the database, you can use the following command: \nmysqlsh --sql -u ${db.username} -P ${db.port}\nIf prompted for a password, leave the password field blank. The database does not have a password set.`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(`If you want to use the MySQL CLI client to connect to the database, you can use either commands: \nmysql -u ${db.username} -P ${db.port} --protocol tcp \nOR\nmysql -u ${db.username} --socket ${db.socket}\nIf prompted for a password, leave the password field blank. The database does not have a password set.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
main();
|
package/dist/src/constants.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { InternalServerOptions, OptionTypeChecks } from "../types";
|
|
2
|
+
export declare const MIN_SUPPORTED_MYSQL = "8.0.20";
|
|
3
|
+
export declare const DEFAULT_OPTIONS_GENERATOR: () => InternalServerOptions;
|
|
4
|
+
export declare const DEFAULT_OPTIONS_KEYS: readonly string[];
|
|
5
|
+
export declare const LOG_LEVELS: {
|
|
6
|
+
readonly LOG: 0;
|
|
7
|
+
readonly WARN: 1;
|
|
8
|
+
readonly ERROR: 2;
|
|
3
9
|
};
|
|
4
|
-
export
|
|
10
|
+
export declare const INTERNAL_OPTIONS: readonly ["_DO_NOT_USE_deleteDBAfterStopped", "_DO_NOT_USE_dbPath", "_DO_NOT_USE_binaryDirectoryPath", "_DO_NOT_USE_beforeSignalCleanup", "_DO_NOT_USE_afterSignalCleanup"];
|
|
11
|
+
export declare const OPTION_TYPE_CHECKS: OptionTypeChecks;
|
package/dist/src/constants.js
CHANGED
|
@@ -1,6 +1,129 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
exports.OPTION_TYPE_CHECKS = exports.INTERNAL_OPTIONS = exports.LOG_LEVELS = exports.DEFAULT_OPTIONS_KEYS = exports.DEFAULT_OPTIONS_GENERATOR = exports.MIN_SUPPORTED_MYSQL = void 0;
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const semver_1 = require("semver");
|
|
8
|
+
exports.MIN_SUPPORTED_MYSQL = '8.0.20';
|
|
9
|
+
const DEFAULT_OPTIONS_GENERATOR = () => ({
|
|
10
|
+
version: undefined,
|
|
11
|
+
dbName: 'dbdata',
|
|
12
|
+
logLevel: 'ERROR',
|
|
13
|
+
portRetries: 10,
|
|
14
|
+
downloadBinaryOnce: true,
|
|
15
|
+
lockRetries: 1000,
|
|
16
|
+
lockRetryWait: 1000,
|
|
17
|
+
username: 'root',
|
|
18
|
+
ignoreUnsupportedSystemVersion: false,
|
|
19
|
+
port: 0,
|
|
20
|
+
xPort: 0,
|
|
21
|
+
downloadRetries: 10,
|
|
22
|
+
initSQLString: '',
|
|
23
|
+
_DO_NOT_USE_deleteDBAfterStopped: true,
|
|
24
|
+
//mysqlmsn = MySQL Memory Server Node.js
|
|
25
|
+
_DO_NOT_USE_dbPath: (0, path_1.normalize)(`${(0, os_1.tmpdir)()}/mysqlmsn/dbs/${(0, crypto_1.randomUUID)().replace(/-/g, '')}`),
|
|
26
|
+
_DO_NOT_USE_binaryDirectoryPath: `${(0, os_1.tmpdir)()}/mysqlmsn/binaries`,
|
|
27
|
+
_DO_NOT_USE_beforeSignalCleanupMessage: '',
|
|
28
|
+
_DO_NOT_USE_afterSignalCleanupMessage: ''
|
|
29
|
+
});
|
|
30
|
+
exports.DEFAULT_OPTIONS_GENERATOR = DEFAULT_OPTIONS_GENERATOR;
|
|
31
|
+
exports.DEFAULT_OPTIONS_KEYS = Object.freeze(Object.keys((0, exports.DEFAULT_OPTIONS_GENERATOR)()));
|
|
32
|
+
exports.LOG_LEVELS = {
|
|
33
|
+
'LOG': 0,
|
|
34
|
+
'WARN': 1,
|
|
35
|
+
'ERROR': 2
|
|
36
|
+
};
|
|
37
|
+
exports.INTERNAL_OPTIONS = ['_DO_NOT_USE_deleteDBAfterStopped', '_DO_NOT_USE_dbPath', '_DO_NOT_USE_binaryDirectoryPath', '_DO_NOT_USE_beforeSignalCleanup', '_DO_NOT_USE_afterSignalCleanup'];
|
|
38
|
+
exports.OPTION_TYPE_CHECKS = {
|
|
39
|
+
version: {
|
|
40
|
+
check: (opt) => opt === undefined || typeof opt === 'string' && (0, semver_1.valid)(opt) !== null,
|
|
41
|
+
errorMessage: 'Option version must be either undefined or a valid semver string.',
|
|
42
|
+
definedType: 'string'
|
|
43
|
+
},
|
|
44
|
+
dbName: {
|
|
45
|
+
check: (opt) => opt === undefined || typeof opt === 'string' && opt.length <= 64,
|
|
46
|
+
errorMessage: 'Option dbName must be either undefined or a string that is not longer than 64 characters.',
|
|
47
|
+
definedType: 'string'
|
|
48
|
+
},
|
|
49
|
+
logLevel: {
|
|
50
|
+
check: (opt) => opt === undefined || Object.keys(exports.LOG_LEVELS).includes(opt),
|
|
51
|
+
errorMessage: 'Option logLevel must be either undefined or "LOG", "WARN", or "ERROR".',
|
|
52
|
+
definedType: 'string'
|
|
53
|
+
},
|
|
54
|
+
portRetries: {
|
|
55
|
+
check: (opt) => opt === undefined || typeof opt === 'number' && opt >= 0,
|
|
56
|
+
errorMessage: 'Option portRetries must be either undefined, a positive number, or 0.',
|
|
57
|
+
definedType: 'number'
|
|
58
|
+
},
|
|
59
|
+
downloadBinaryOnce: {
|
|
60
|
+
check: (opt) => opt === undefined || typeof opt === 'boolean',
|
|
61
|
+
errorMessage: 'Option downloadBinaryOnce must be either undefined or a boolean.',
|
|
62
|
+
definedType: 'boolean'
|
|
63
|
+
},
|
|
64
|
+
lockRetries: {
|
|
65
|
+
check: (opt) => opt === undefined || typeof opt === 'number' && opt >= 0,
|
|
66
|
+
errorMessage: 'Option lockRetries must be either undefined, a positive number, or 0.',
|
|
67
|
+
definedType: 'number'
|
|
68
|
+
},
|
|
69
|
+
lockRetryWait: {
|
|
70
|
+
check: (opt) => opt === undefined || typeof opt === 'number' && opt >= 0,
|
|
71
|
+
errorMessage: 'Option lockRetryWait must be either undefined, a positive number, or 0.',
|
|
72
|
+
definedType: 'number'
|
|
73
|
+
},
|
|
74
|
+
username: {
|
|
75
|
+
check: (opt) => opt === undefined || typeof opt === 'string' && opt.length <= 32,
|
|
76
|
+
errorMessage: 'Option username must be either undefined or a string that is not longer than 32 characters.',
|
|
77
|
+
definedType: 'string'
|
|
78
|
+
},
|
|
79
|
+
ignoreUnsupportedSystemVersion: {
|
|
80
|
+
check: (opt) => opt === undefined || typeof opt === 'boolean',
|
|
81
|
+
errorMessage: 'Option ignoreUnsupportedSystemVersion must be either undefined or a boolean.',
|
|
82
|
+
definedType: 'boolean'
|
|
83
|
+
},
|
|
84
|
+
port: {
|
|
85
|
+
check: (opt) => opt === undefined || typeof opt === 'number' && opt >= 0 && opt <= 65535,
|
|
86
|
+
errorMessage: 'Option port must be either undefined or a number that is between 0 and 65535 inclusive.',
|
|
87
|
+
definedType: 'number'
|
|
88
|
+
},
|
|
89
|
+
xPort: {
|
|
90
|
+
check: (opt) => opt === undefined || typeof opt === 'number' && opt >= 0 && opt <= 65535,
|
|
91
|
+
errorMessage: 'Option xPort must be either undefined or a number that is between 0 and 65535 inclusive.',
|
|
92
|
+
definedType: 'number'
|
|
93
|
+
},
|
|
94
|
+
downloadRetries: {
|
|
95
|
+
check: (opt) => opt === undefined || typeof opt === 'number' && opt >= 0,
|
|
96
|
+
errorMessage: 'Option downloadRetries must be either undefined, a positive number, or 0.',
|
|
97
|
+
definedType: 'number'
|
|
98
|
+
},
|
|
99
|
+
initSQLString: {
|
|
100
|
+
check: (opt) => opt === undefined || typeof opt === 'string',
|
|
101
|
+
errorMessage: 'Option initSQLString must be either undefined or a string.',
|
|
102
|
+
definedType: 'string'
|
|
103
|
+
},
|
|
104
|
+
_DO_NOT_USE_deleteDBAfterStopped: {
|
|
105
|
+
check: (opt) => opt === undefined || typeof opt === 'boolean',
|
|
106
|
+
errorMessage: 'Option _DO_NOT_USE_deleteDBAfterStopped must be either undefined or a boolean.',
|
|
107
|
+
definedType: 'boolean'
|
|
108
|
+
},
|
|
109
|
+
_DO_NOT_USE_dbPath: {
|
|
110
|
+
check: (opt) => opt === undefined || typeof opt === 'string',
|
|
111
|
+
errorMessage: 'Option _DO_NOT_USE_dbPath must be either undefined or a string.',
|
|
112
|
+
definedType: 'string'
|
|
113
|
+
},
|
|
114
|
+
_DO_NOT_USE_binaryDirectoryPath: {
|
|
115
|
+
check: (opt) => opt === undefined || typeof opt === 'string',
|
|
116
|
+
errorMessage: 'Option _DO_NOT_USE_binaryDirectoryPath must be either undefined or a string.',
|
|
117
|
+
definedType: 'string'
|
|
118
|
+
},
|
|
119
|
+
_DO_NOT_USE_beforeSignalCleanupMessage: {
|
|
120
|
+
check: (opt) => opt === undefined || typeof opt === 'string',
|
|
121
|
+
errorMessage: 'Option _DO_NOT_USE_beforeSignalCleanup must be either undefined or a string.',
|
|
122
|
+
definedType: 'string'
|
|
123
|
+
},
|
|
124
|
+
_DO_NOT_USE_afterSignalCleanupMessage: {
|
|
125
|
+
check: (opt) => opt === undefined || typeof opt === 'string',
|
|
126
|
+
errorMessage: 'Option _DO_NOT_USE_afterSignalCleanup must be either undefined or a string.',
|
|
127
|
+
definedType: 'string'
|
|
128
|
+
}
|
|
5
129
|
};
|
|
6
|
-
exports.default = CONSTANTS;
|
package/dist/src/index.js
CHANGED
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
37
|
};
|
|
@@ -34,38 +44,37 @@ const semver_1 = require("semver");
|
|
|
34
44
|
const Version_1 = __importDefault(require("./libraries/Version"));
|
|
35
45
|
const versions_json_1 = __importDefault(require("./versions.json"));
|
|
36
46
|
const Downloader_1 = require("./libraries/Downloader");
|
|
37
|
-
const
|
|
38
|
-
const path_1 = require("path");
|
|
39
|
-
const constants_1 = __importDefault(require("./constants"));
|
|
47
|
+
const constants_1 = require("./constants");
|
|
40
48
|
async function createDB(opts) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
const suppliedOpts = opts || {};
|
|
50
|
+
const suppliedOptsKeys = Object.keys(suppliedOpts);
|
|
51
|
+
for (const opt of constants_1.INTERNAL_OPTIONS) {
|
|
52
|
+
if (suppliedOptsKeys.includes(opt)) {
|
|
53
|
+
console.warn(`[ mysql-memory-server - Options WARN ]: Creating MySQL database with option ${opt}. This is considered unstable and should not be used externally. Please consider removing this option.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const options = (0, constants_1.DEFAULT_OPTIONS_GENERATOR)();
|
|
57
|
+
for (const opt of suppliedOptsKeys) {
|
|
58
|
+
if (!constants_1.DEFAULT_OPTIONS_KEYS.includes(opt)) {
|
|
59
|
+
throw `Option ${opt} is not a valid option.`;
|
|
60
|
+
}
|
|
61
|
+
if (!constants_1.OPTION_TYPE_CHECKS[opt].check(suppliedOpts[opt])) {
|
|
62
|
+
//Supplied option failed the check
|
|
63
|
+
throw constants_1.OPTION_TYPE_CHECKS[opt].errorMessage;
|
|
64
|
+
}
|
|
65
|
+
if (suppliedOpts[opt] !== undefined) {
|
|
66
|
+
options[opt] = suppliedOpts[opt];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
60
69
|
const logger = new Logger_1.default(options.logLevel);
|
|
61
70
|
const executor = new Executor_1.default(logger);
|
|
62
71
|
const version = await executor.getMySQLVersion(options.version);
|
|
63
|
-
const unsupportedMySQLIsInstalled = version && (0, semver_1.lt)(version.version, constants_1.
|
|
72
|
+
const unsupportedMySQLIsInstalled = version && (0, semver_1.lt)(version.version, constants_1.MIN_SUPPORTED_MYSQL);
|
|
64
73
|
const throwUnsupportedError = unsupportedMySQLIsInstalled && !options.ignoreUnsupportedSystemVersion && !options.version;
|
|
65
74
|
if (throwUnsupportedError) {
|
|
66
75
|
throw `A version of MySQL is installed on your system that is not supported by this package. If you want to download a MySQL binary instead of getting this error, please set the option "ignoreUnsupportedSystemVersion" to true.`;
|
|
67
76
|
}
|
|
68
|
-
if (options.version && (0, semver_1.lt)(options.version, constants_1.
|
|
77
|
+
if (options.version && (0, semver_1.lt)(options.version, constants_1.MIN_SUPPORTED_MYSQL)) {
|
|
69
78
|
//The difference between the throw here and the throw above is this throw is because the selected "version" is not supported.
|
|
70
79
|
//The throw above is because the system-installed MySQL is out of date and "ignoreUnsupportedSystemVersion" is not set to true.
|
|
71
80
|
throw `The selected version of MySQL (${options.version}) is not currently supported by this package. Please choose a different version to use.`;
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
37
|
};
|
|
@@ -35,7 +45,6 @@ const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
|
35
45
|
const path_1 = require("path");
|
|
36
46
|
const crypto_1 = require("crypto");
|
|
37
47
|
const child_process_1 = require("child_process");
|
|
38
|
-
const proper_lockfile_1 = require("proper-lockfile");
|
|
39
48
|
const FileLock_1 = require("./FileLock");
|
|
40
49
|
function getZipData(entry) {
|
|
41
50
|
return new Promise((resolve, reject) => {
|
|
@@ -199,7 +208,7 @@ function extractBinary(url, archiveLocation, extractedLocation, logger) {
|
|
|
199
208
|
function downloadBinary(binaryInfo, options, logger) {
|
|
200
209
|
return new Promise(async (resolve, reject) => {
|
|
201
210
|
const { url, version } = binaryInfo;
|
|
202
|
-
const dirpath = options.
|
|
211
|
+
const dirpath = options._DO_NOT_USE_binaryDirectoryPath;
|
|
203
212
|
logger.log('Binary path:', dirpath);
|
|
204
213
|
await fsPromises.mkdir(dirpath, { recursive: true });
|
|
205
214
|
const lastDashIndex = url.lastIndexOf('-');
|
|
@@ -215,11 +224,11 @@ function downloadBinary(binaryInfo, options, logger) {
|
|
|
215
224
|
let releaseFunction;
|
|
216
225
|
while (true) {
|
|
217
226
|
try {
|
|
218
|
-
releaseFunction = (0,
|
|
227
|
+
releaseFunction = await (0, FileLock_1.lockFile)(extractedPath);
|
|
219
228
|
break;
|
|
220
229
|
}
|
|
221
230
|
catch (e) {
|
|
222
|
-
if (e
|
|
231
|
+
if (e === 'LOCKED') {
|
|
223
232
|
logger.log('Waiting for lock for MySQL version', version);
|
|
224
233
|
await (0, FileLock_1.waitForLock)(extractedPath, options);
|
|
225
234
|
logger.log('Lock is gone for version', version);
|
|
@@ -253,10 +262,10 @@ function downloadBinary(binaryInfo, options, logger) {
|
|
|
253
262
|
catch (e) {
|
|
254
263
|
logger.error('An error occurred while deleting extractedPath and/or archivePath:', e);
|
|
255
264
|
}
|
|
256
|
-
if (downloadTries
|
|
265
|
+
if (downloadTries > options.downloadRetries) {
|
|
257
266
|
//Only reject if we have met the downloadRetries limit
|
|
258
267
|
try {
|
|
259
|
-
releaseFunction();
|
|
268
|
+
await releaseFunction();
|
|
260
269
|
}
|
|
261
270
|
catch (e) {
|
|
262
271
|
logger.error('An error occurred while releasing lock after downloadRetries exhaustion. The error was:', e);
|
|
@@ -268,7 +277,7 @@ function downloadBinary(binaryInfo, options, logger) {
|
|
|
268
277
|
console.warn(`An error was encountered during the binary download process. Retrying for retry ${downloadTries}/${options.downloadRetries}. The error was:`, e);
|
|
269
278
|
}
|
|
270
279
|
}
|
|
271
|
-
} while (downloadTries
|
|
280
|
+
} while (downloadTries <= options.downloadRetries);
|
|
272
281
|
try {
|
|
273
282
|
releaseFunction();
|
|
274
283
|
}
|
|
@@ -301,7 +310,7 @@ function downloadBinary(binaryInfo, options, logger) {
|
|
|
301
310
|
catch (e) {
|
|
302
311
|
logger.error('An error occurred while deleting extractedPath and/or archivePath:', e);
|
|
303
312
|
}
|
|
304
|
-
if (downloadTries
|
|
313
|
+
if (downloadTries > options.downloadRetries) {
|
|
305
314
|
//Only reject if we have met the downloadRetries limit
|
|
306
315
|
return reject(e);
|
|
307
316
|
}
|
|
@@ -309,7 +318,7 @@ function downloadBinary(binaryInfo, options, logger) {
|
|
|
309
318
|
console.warn(`An error was encountered during the binary download process. Retrying for retry ${downloadTries}/${options.downloadRetries}. The error was:`, e);
|
|
310
319
|
}
|
|
311
320
|
}
|
|
312
|
-
} while (downloadTries
|
|
321
|
+
} while (downloadTries <= options.downloadRetries);
|
|
313
322
|
}
|
|
314
323
|
});
|
|
315
324
|
}
|
|
@@ -3,6 +3,8 @@ import { InstalledMySQLVersion, InternalServerOptions, MySQLDB } from "../../typ
|
|
|
3
3
|
declare class Executor {
|
|
4
4
|
#private;
|
|
5
5
|
logger: Logger;
|
|
6
|
+
DBDestroySignal: AbortController;
|
|
7
|
+
removeExitHandler: () => void;
|
|
6
8
|
constructor(logger: Logger);
|
|
7
9
|
getMySQLVersion(preferredVersion?: string): Promise<InstalledMySQLVersion | null>;
|
|
8
10
|
startMySQL(options: InternalServerOptions, binaryFilepath: string): Promise<MySQLDB>;
|
|
@@ -15,22 +15,29 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
26
36
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
27
37
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
28
38
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
29
39
|
};
|
|
30
|
-
var
|
|
31
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
-
};
|
|
33
|
-
var _Executor_instances, _Executor_executeFile, _Executor_killProcess, _Executor_deleteDatabaseDirectory, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
|
|
40
|
+
var _Executor_instances, _Executor_executeFile, _Executor_killProcess, _Executor_deleteDatabaseDirectory, _Executor_returnBinaryPathToDelete, _Executor_startMySQLProcess, _Executor_setupDataDirectories;
|
|
34
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
42
|
const child_process_1 = require("child_process");
|
|
36
43
|
const semver_1 = require("semver");
|
|
@@ -38,13 +45,14 @@ const os = __importStar(require("os"));
|
|
|
38
45
|
const fsPromises = __importStar(require("fs/promises"));
|
|
39
46
|
const fs = __importStar(require("fs"));
|
|
40
47
|
const Port_1 = require("./Port");
|
|
41
|
-
const AbortSignal_1 = __importDefault(require("./AbortSignal"));
|
|
42
48
|
const path_1 = require("path");
|
|
43
|
-
const proper_lockfile_1 = require("proper-lockfile");
|
|
44
49
|
const FileLock_1 = require("./FileLock");
|
|
50
|
+
const signal_exit_1 = require("signal-exit");
|
|
51
|
+
const crypto_1 = require("crypto");
|
|
45
52
|
class Executor {
|
|
46
53
|
constructor(logger) {
|
|
47
54
|
_Executor_instances.add(this);
|
|
55
|
+
this.DBDestroySignal = new AbortController();
|
|
48
56
|
this.logger = logger;
|
|
49
57
|
}
|
|
50
58
|
getMySQLVersion(preferredVersion) {
|
|
@@ -107,8 +115,34 @@ class Executor {
|
|
|
107
115
|
});
|
|
108
116
|
}
|
|
109
117
|
async startMySQL(options, binaryFilepath) {
|
|
118
|
+
this.removeExitHandler = (0, signal_exit_1.onExit)(() => {
|
|
119
|
+
if (options._DO_NOT_USE_beforeSignalCleanupMessage) {
|
|
120
|
+
console.log(options._DO_NOT_USE_beforeSignalCleanupMessage);
|
|
121
|
+
}
|
|
122
|
+
this.DBDestroySignal.abort();
|
|
123
|
+
if (options._DO_NOT_USE_deleteDBAfterStopped) {
|
|
124
|
+
try {
|
|
125
|
+
fs.rmSync(options._DO_NOT_USE_dbPath, { recursive: true, maxRetries: 50, force: true });
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
this.logger.error('An error occurred while deleting database directory path:', e);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const binaryPathToDelete = __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_returnBinaryPathToDelete).call(this, binaryFilepath, options);
|
|
132
|
+
if (binaryPathToDelete) {
|
|
133
|
+
try {
|
|
134
|
+
fs.rmSync(binaryPathToDelete, { force: true, recursive: true, maxRetries: 50 });
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
this.logger.error('An error occurred while deleting database binary:', e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (options._DO_NOT_USE_afterSignalCleanupMessage) {
|
|
141
|
+
console.log(options._DO_NOT_USE_afterSignalCleanupMessage);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
110
144
|
let retries = 0;
|
|
111
|
-
const datadir = (0, path_1.normalize)(`${options.
|
|
145
|
+
const datadir = (0, path_1.normalize)(`${options._DO_NOT_USE_dbPath}/data`);
|
|
112
146
|
do {
|
|
113
147
|
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir, true);
|
|
114
148
|
this.logger.log('Setting up directories was successful');
|
|
@@ -117,7 +151,7 @@ class Executor {
|
|
|
117
151
|
this.logger.log('Using port:', port, 'and MySQLX port:', mySQLXPort, 'on retry:', retries);
|
|
118
152
|
try {
|
|
119
153
|
this.logger.log('Starting MySQL process');
|
|
120
|
-
const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, options.
|
|
154
|
+
const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, options._DO_NOT_USE_dbPath, binaryFilepath);
|
|
121
155
|
this.logger.log('Starting process was successful');
|
|
122
156
|
return resolved;
|
|
123
157
|
}
|
|
@@ -140,7 +174,7 @@ class Executor {
|
|
|
140
174
|
}
|
|
141
175
|
_Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_executeFile(command, args) {
|
|
142
176
|
return new Promise(resolve => {
|
|
143
|
-
(0, child_process_1.execFile)(command, args, { signal:
|
|
177
|
+
(0, child_process_1.execFile)(command, args, { signal: this.DBDestroySignal.signal }, (error, stdout, stderr) => {
|
|
144
178
|
resolve({ error, stdout, stderr });
|
|
145
179
|
});
|
|
146
180
|
});
|
|
@@ -181,13 +215,24 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
181
215
|
this.logger.log('DB data directory deletion failed. Now on retry', retries);
|
|
182
216
|
}
|
|
183
217
|
}
|
|
218
|
+
}, _Executor_returnBinaryPathToDelete = function _Executor_returnBinaryPathToDelete(binaryFilepath, options) {
|
|
219
|
+
if (binaryFilepath.includes(os.tmpdir()) && !options.downloadBinaryOnce) {
|
|
220
|
+
const splitPath = binaryFilepath.split(os.platform() === 'win32' ? '\\' : '/');
|
|
221
|
+
const binariesIndex = splitPath.indexOf('binaries');
|
|
222
|
+
//The path will be the directory path for the binary download
|
|
223
|
+
splitPath.splice(binariesIndex + 2);
|
|
224
|
+
return splitPath.join('/');
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
184
227
|
}, _Executor_startMySQLProcess = function _Executor_startMySQLProcess(options, port, mySQLXPort, datadir, dbPath, binaryFilepath) {
|
|
185
228
|
const errors = [];
|
|
186
229
|
const logFile = `${dbPath}/log.log`;
|
|
187
230
|
const errorLogFile = `${datadir}/errorlog.err`;
|
|
188
231
|
return new Promise(async (resolve, reject) => {
|
|
189
232
|
await fsPromises.rm(logFile, { force: true });
|
|
190
|
-
const
|
|
233
|
+
const socket = os.platform() === 'win32' ? `MySQL-${(0, crypto_1.randomUUID)()}` : `${dbPath}/m.sock`;
|
|
234
|
+
const xSocket = os.platform() === 'win32' ? `MySQLX-${(0, crypto_1.randomUUID)()}` : `${dbPath}/x.sock`;
|
|
235
|
+
const process = (0, child_process_1.spawn)(binaryFilepath, ['--no-defaults', `--port=${port}`, `--datadir=${datadir}`, `--mysqlx-port=${mySQLXPort}`, `--mysqlx-socket=${xSocket}`, `--socket=${socket}`, `--general-log-file=${logFile}`, '--general-log=1', `--init-file=${dbPath}/init.sql`, '--bind-address=127.0.0.1', '--innodb-doublewrite=OFF', '--mysqlx=FORCE', `--log-error=${errorLogFile}`, `--user=${os.userInfo().username}`], { signal: this.DBDestroySignal.signal, killSignal: 'SIGKILL' });
|
|
191
236
|
//resolveFunction is the function that will be called to resolve the promise that stops the database.
|
|
192
237
|
//If resolveFunction is not undefined, the database has received a kill signal and data cleanup procedures should run.
|
|
193
238
|
//Once ran, resolveFunction will be called.
|
|
@@ -206,7 +251,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
206
251
|
if (portIssue || xPortIssue) {
|
|
207
252
|
this.logger.log('Error log when exiting for port in use error:', errorLog);
|
|
208
253
|
try {
|
|
209
|
-
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, options.
|
|
254
|
+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, options._DO_NOT_USE_dbPath);
|
|
210
255
|
}
|
|
211
256
|
catch (e) {
|
|
212
257
|
this.logger.error(e);
|
|
@@ -215,22 +260,18 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
215
260
|
return reject('Port is already in use');
|
|
216
261
|
}
|
|
217
262
|
try {
|
|
218
|
-
if (options.
|
|
263
|
+
if (options._DO_NOT_USE_deleteDBAfterStopped) {
|
|
219
264
|
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, dbPath);
|
|
220
265
|
}
|
|
221
266
|
}
|
|
222
267
|
catch (e) {
|
|
223
|
-
this.logger.error('An
|
|
268
|
+
this.logger.error('An error occurred while deleting database directory at path:', dbPath, '| The error was:', e);
|
|
224
269
|
}
|
|
225
270
|
finally {
|
|
226
271
|
try {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
//The path will be the directory path for the binary download
|
|
231
|
-
splitPath.splice(binariesIndex + 2);
|
|
232
|
-
//Delete the binary folder
|
|
233
|
-
await fsPromises.rm(splitPath.join('/'), { force: true, recursive: true });
|
|
272
|
+
const binaryPathToDelete = __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_returnBinaryPathToDelete).call(this, binaryFilepath, options);
|
|
273
|
+
if (binaryPathToDelete) {
|
|
274
|
+
await fsPromises.rm(binaryPathToDelete, { force: true, recursive: true, maxRetries: 50 });
|
|
234
275
|
}
|
|
235
276
|
}
|
|
236
277
|
catch (e) {
|
|
@@ -282,11 +323,14 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
282
323
|
resolve({
|
|
283
324
|
port,
|
|
284
325
|
xPort: mySQLXPort,
|
|
326
|
+
socket,
|
|
327
|
+
xSocket,
|
|
285
328
|
dbName: options.dbName,
|
|
286
329
|
username: options.username,
|
|
287
330
|
stop: () => {
|
|
288
331
|
return new Promise(async (resolve, reject) => {
|
|
289
332
|
resolveFunction = resolve;
|
|
333
|
+
this.removeExitHandler();
|
|
290
334
|
const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
|
|
291
335
|
if (!killed) {
|
|
292
336
|
reject();
|
|
@@ -354,11 +398,11 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
354
398
|
let lockRelease;
|
|
355
399
|
while (true) {
|
|
356
400
|
try {
|
|
357
|
-
lockRelease = (0,
|
|
401
|
+
lockRelease = await (0, FileLock_1.lockFile)(copyPath);
|
|
358
402
|
break;
|
|
359
403
|
}
|
|
360
404
|
catch (e) {
|
|
361
|
-
if (e
|
|
405
|
+
if (e === 'LOCKED') {
|
|
362
406
|
this.logger.log('Waiting for lock for libaio copy');
|
|
363
407
|
await (0, FileLock_1.waitForLock)(copyPath, options);
|
|
364
408
|
this.logger.log('Lock is gone for libaio copy');
|
|
@@ -396,7 +440,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
396
440
|
}
|
|
397
441
|
finally {
|
|
398
442
|
try {
|
|
399
|
-
lockRelease();
|
|
443
|
+
await lockRelease();
|
|
400
444
|
}
|
|
401
445
|
catch (e) {
|
|
402
446
|
this.logger.error('Error unlocking libaio file:', e);
|
|
@@ -427,7 +471,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
427
471
|
initText += `\n${options.initSQLString}`;
|
|
428
472
|
}
|
|
429
473
|
this.logger.log('Writing init file');
|
|
430
|
-
await fsPromises.writeFile(`${options.
|
|
474
|
+
await fsPromises.writeFile(`${options._DO_NOT_USE_dbPath}/init.sql`, initText, { encoding: 'utf8' });
|
|
431
475
|
this.logger.log('Finished writing init file');
|
|
432
476
|
};
|
|
433
477
|
exports.default = Executor;
|
|
@@ -1,25 +1,81 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.waitForLock = waitForLock;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
exports.lockFile = lockFile;
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const mtimeUpdateIntervalTime = 2000;
|
|
10
|
+
const mtimeLimit = 10000;
|
|
11
|
+
async function waitForLock(path, options) {
|
|
12
|
+
const lockPath = `${path}.lock`;
|
|
13
|
+
let retries = 0;
|
|
14
|
+
do {
|
|
15
|
+
retries++;
|
|
16
|
+
try {
|
|
17
|
+
const stat = await promises_1.default.stat(lockPath);
|
|
18
|
+
if (Date.now() - stat.mtime.getTime() > mtimeLimit) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
await new Promise(resolve => setTimeout(resolve, options.lockRetryWait));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
if (e.code === 'ENOENT') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} while (retries <= options.lockRetries);
|
|
34
|
+
throw `lockRetries has been exceeded. Lock had not been released after ${options.lockRetryWait} * ${options.lockRetries} (${options.lockRetryWait * options.lockRetries}) milliseconds.`;
|
|
35
|
+
}
|
|
36
|
+
function setupMTimeEditor(lockPath) {
|
|
37
|
+
const interval = setInterval(async () => {
|
|
38
|
+
try {
|
|
39
|
+
const time = new Date();
|
|
40
|
+
await promises_1.default.utimes(lockPath, time, time);
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
}, mtimeUpdateIntervalTime);
|
|
44
|
+
return async () => {
|
|
45
|
+
clearInterval(interval);
|
|
46
|
+
await promises_1.default.rmdir(lockPath);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
async function lockFile(path) {
|
|
50
|
+
const lockPath = `${path}.lock`;
|
|
51
|
+
try {
|
|
52
|
+
await promises_1.default.mkdir(lockPath);
|
|
53
|
+
return setupMTimeEditor(lockPath);
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
if (e.code === 'EEXIST') {
|
|
10
57
|
try {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
return
|
|
58
|
+
const stat = await promises_1.default.stat(lockPath);
|
|
59
|
+
if (Date.now() - stat.mtime.getTime() > mtimeLimit) {
|
|
60
|
+
return setupMTimeEditor(lockPath);
|
|
14
61
|
}
|
|
15
62
|
else {
|
|
16
|
-
|
|
63
|
+
throw 'LOCKED';
|
|
17
64
|
}
|
|
18
65
|
}
|
|
19
66
|
catch (e) {
|
|
20
|
-
|
|
67
|
+
if (e.code === 'ENOENT') {
|
|
68
|
+
//This will run if the lock gets released after the EEXIST error is thrown but before the stat is checked.
|
|
69
|
+
//If this is the case, the lock acquisition should be retried.
|
|
70
|
+
return await lockFile(path);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
21
75
|
}
|
|
22
76
|
}
|
|
23
|
-
|
|
24
|
-
|
|
77
|
+
else {
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
25
81
|
}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
'LOG': 0,
|
|
5
|
-
'WARN': 1,
|
|
6
|
-
'ERROR': 2
|
|
7
|
-
};
|
|
3
|
+
const constants_1 = require("../constants");
|
|
8
4
|
class Logger {
|
|
9
5
|
constructor(level) {
|
|
10
|
-
this.LOG_LEVEL = LOG_LEVELS[level];
|
|
6
|
+
this.LOG_LEVEL = constants_1.LOG_LEVELS[level];
|
|
11
7
|
}
|
|
12
8
|
log(...args) {
|
|
13
9
|
if (this.LOG_LEVEL === 0) {
|
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
36
|
exports.default = getBinaryURL;
|
|
27
37
|
const os = __importStar(require("os"));
|
package/dist/types/index.d.ts
CHANGED
|
@@ -9,14 +9,16 @@ export type ServerOptions = {
|
|
|
9
9
|
lockRetries?: number | undefined;
|
|
10
10
|
lockRetryWait?: number | undefined;
|
|
11
11
|
username?: string | undefined;
|
|
12
|
-
deleteDBAfterStopped?: boolean | undefined;
|
|
13
|
-
dbPath?: string | undefined;
|
|
14
12
|
ignoreUnsupportedSystemVersion?: boolean | undefined;
|
|
15
13
|
port?: number | undefined;
|
|
16
14
|
xPort?: number | undefined;
|
|
17
|
-
binaryDirectoryPath?: string | undefined;
|
|
18
15
|
downloadRetries?: number | undefined;
|
|
19
16
|
initSQLString?: string | undefined;
|
|
17
|
+
_DO_NOT_USE_deleteDBAfterStopped?: boolean | undefined;
|
|
18
|
+
_DO_NOT_USE_dbPath?: string | undefined;
|
|
19
|
+
_DO_NOT_USE_binaryDirectoryPath?: string | undefined;
|
|
20
|
+
_DO_NOT_USE_beforeSignalCleanupMessage?: string | undefined;
|
|
21
|
+
_DO_NOT_USE_afterSignalCleanupMessage?: string | undefined;
|
|
20
22
|
};
|
|
21
23
|
export type InternalServerOptions = {
|
|
22
24
|
version?: string | undefined;
|
|
@@ -27,14 +29,16 @@ export type InternalServerOptions = {
|
|
|
27
29
|
lockRetries: number;
|
|
28
30
|
lockRetryWait: number;
|
|
29
31
|
username: string;
|
|
30
|
-
deleteDBAfterStopped: boolean;
|
|
31
|
-
dbPath: string;
|
|
32
32
|
ignoreUnsupportedSystemVersion: boolean;
|
|
33
33
|
port: number;
|
|
34
34
|
xPort: number;
|
|
35
|
-
binaryDirectoryPath: string;
|
|
36
35
|
downloadRetries: number;
|
|
37
36
|
initSQLString: string;
|
|
37
|
+
_DO_NOT_USE_deleteDBAfterStopped: boolean;
|
|
38
|
+
_DO_NOT_USE_dbPath: string;
|
|
39
|
+
_DO_NOT_USE_binaryDirectoryPath: string;
|
|
40
|
+
_DO_NOT_USE_beforeSignalCleanupMessage: string;
|
|
41
|
+
_DO_NOT_USE_afterSignalCleanupMessage: string;
|
|
38
42
|
};
|
|
39
43
|
export type ExecutorOptions = {
|
|
40
44
|
logLevel: LOG_LEVEL;
|
|
@@ -47,6 +51,8 @@ export type ExecuteFileReturn = {
|
|
|
47
51
|
export type MySQLDB = {
|
|
48
52
|
port: number;
|
|
49
53
|
xPort: number;
|
|
54
|
+
socket: string;
|
|
55
|
+
xSocket: string;
|
|
50
56
|
dbName: string;
|
|
51
57
|
username: string;
|
|
52
58
|
stop: () => Promise<void>;
|
|
@@ -66,3 +72,10 @@ export type BinaryInfo = {
|
|
|
66
72
|
url: string;
|
|
67
73
|
version: string;
|
|
68
74
|
};
|
|
75
|
+
export type OptionTypeChecks = {
|
|
76
|
+
[key in keyof Required<ServerOptions>]: {
|
|
77
|
+
check: (opt: any) => boolean;
|
|
78
|
+
errorMessage: string;
|
|
79
|
+
definedType: "string" | "boolean" | "number";
|
|
80
|
+
};
|
|
81
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mysql-memory-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Spin up an ephemeral MySQL database from your JavaScript code",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -31,18 +31,17 @@
|
|
|
31
31
|
"@babel/preset-env": "^7.25.9",
|
|
32
32
|
"@babel/preset-typescript": "^7.25.9",
|
|
33
33
|
"@types/adm-zip": "^0.5.5",
|
|
34
|
-
"@types/node": "^22.
|
|
35
|
-
"@types/proper-lockfile": "^4.1.4",
|
|
34
|
+
"@types/node": "^22.9.3",
|
|
36
35
|
"@types/semver": "^7.5.8",
|
|
37
36
|
"babel-jest": "^29.7.0",
|
|
38
37
|
"jest": "^29.7.0",
|
|
39
38
|
"mysql2": "^3.11.3",
|
|
40
|
-
"typescript": "^5.
|
|
39
|
+
"typescript": "^5.7.2"
|
|
41
40
|
},
|
|
42
41
|
"dependencies": {
|
|
43
42
|
"adm-zip": "^0.5.16",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
43
|
+
"semver": "^7.6.3",
|
|
44
|
+
"signal-exit": "^4.1.0"
|
|
46
45
|
},
|
|
47
46
|
"repository": {
|
|
48
47
|
"type": "git",
|
|
@@ -51,5 +50,6 @@
|
|
|
51
50
|
"bugs": {
|
|
52
51
|
"url": "https://github.com/Sebastian-Webster/mysql-memory-server-nodejs/issues"
|
|
53
52
|
},
|
|
54
|
-
"homepage": "https://github.com/Sebastian-Webster/mysql-memory-server-nodejs"
|
|
53
|
+
"homepage": "https://github.com/Sebastian-Webster/mysql-memory-server-nodejs",
|
|
54
|
+
"bin": "dist/src/cli.js"
|
|
55
55
|
}
|