mysql-memory-server 1.6.0 → 1.7.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 +37 -15
- 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 +74 -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 --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
|
|
|
@@ -209,28 +221,38 @@ The internal queries that are ran before the queries in ```initSQLString``` are
|
|
|
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 epehemeral 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 databases 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,13 @@ 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");
|
|
45
51
|
class Executor {
|
|
46
52
|
constructor(logger) {
|
|
47
53
|
_Executor_instances.add(this);
|
|
54
|
+
this.DBDestroySignal = new AbortController();
|
|
48
55
|
this.logger = logger;
|
|
49
56
|
}
|
|
50
57
|
getMySQLVersion(preferredVersion) {
|
|
@@ -107,8 +114,34 @@ class Executor {
|
|
|
107
114
|
});
|
|
108
115
|
}
|
|
109
116
|
async startMySQL(options, binaryFilepath) {
|
|
117
|
+
this.removeExitHandler = (0, signal_exit_1.onExit)(() => {
|
|
118
|
+
if (options._DO_NOT_USE_beforeSignalCleanupMessage) {
|
|
119
|
+
console.log(options._DO_NOT_USE_beforeSignalCleanupMessage);
|
|
120
|
+
}
|
|
121
|
+
this.DBDestroySignal.abort();
|
|
122
|
+
if (options._DO_NOT_USE_deleteDBAfterStopped) {
|
|
123
|
+
try {
|
|
124
|
+
fs.rmSync(options._DO_NOT_USE_dbPath, { recursive: true, maxRetries: 50, force: true });
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
this.logger.error('An error occurred while deleting database directory path:', e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const binaryPathToDelete = __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_returnBinaryPathToDelete).call(this, binaryFilepath, options);
|
|
131
|
+
if (binaryPathToDelete) {
|
|
132
|
+
try {
|
|
133
|
+
fs.rmSync(binaryPathToDelete, { force: true, recursive: true, maxRetries: 50 });
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
this.logger.error('An error occurred while deleting database binary:', e);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (options._DO_NOT_USE_afterSignalCleanupMessage) {
|
|
140
|
+
console.log(options._DO_NOT_USE_afterSignalCleanupMessage);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
110
143
|
let retries = 0;
|
|
111
|
-
const datadir = (0, path_1.normalize)(`${options.
|
|
144
|
+
const datadir = (0, path_1.normalize)(`${options._DO_NOT_USE_dbPath}/data`);
|
|
112
145
|
do {
|
|
113
146
|
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_setupDataDirectories).call(this, options, binaryFilepath, datadir, true);
|
|
114
147
|
this.logger.log('Setting up directories was successful');
|
|
@@ -117,7 +150,7 @@ class Executor {
|
|
|
117
150
|
this.logger.log('Using port:', port, 'and MySQLX port:', mySQLXPort, 'on retry:', retries);
|
|
118
151
|
try {
|
|
119
152
|
this.logger.log('Starting MySQL process');
|
|
120
|
-
const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, options.
|
|
153
|
+
const resolved = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_startMySQLProcess).call(this, options, port, mySQLXPort, datadir, options._DO_NOT_USE_dbPath, binaryFilepath);
|
|
121
154
|
this.logger.log('Starting process was successful');
|
|
122
155
|
return resolved;
|
|
123
156
|
}
|
|
@@ -140,7 +173,7 @@ class Executor {
|
|
|
140
173
|
}
|
|
141
174
|
_Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_executeFile(command, args) {
|
|
142
175
|
return new Promise(resolve => {
|
|
143
|
-
(0, child_process_1.execFile)(command, args, { signal:
|
|
176
|
+
(0, child_process_1.execFile)(command, args, { signal: this.DBDestroySignal.signal }, (error, stdout, stderr) => {
|
|
144
177
|
resolve({ error, stdout, stderr });
|
|
145
178
|
});
|
|
146
179
|
});
|
|
@@ -181,13 +214,24 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
181
214
|
this.logger.log('DB data directory deletion failed. Now on retry', retries);
|
|
182
215
|
}
|
|
183
216
|
}
|
|
217
|
+
}, _Executor_returnBinaryPathToDelete = function _Executor_returnBinaryPathToDelete(binaryFilepath, options) {
|
|
218
|
+
if (binaryFilepath.includes(os.tmpdir()) && !options.downloadBinaryOnce) {
|
|
219
|
+
const splitPath = binaryFilepath.split(os.platform() === 'win32' ? '\\' : '/');
|
|
220
|
+
const binariesIndex = splitPath.indexOf('binaries');
|
|
221
|
+
//The path will be the directory path for the binary download
|
|
222
|
+
splitPath.splice(binariesIndex + 2);
|
|
223
|
+
return splitPath.join('/');
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
184
226
|
}, _Executor_startMySQLProcess = function _Executor_startMySQLProcess(options, port, mySQLXPort, datadir, dbPath, binaryFilepath) {
|
|
185
227
|
const errors = [];
|
|
186
228
|
const logFile = `${dbPath}/log.log`;
|
|
187
229
|
const errorLogFile = `${datadir}/errorlog.err`;
|
|
188
230
|
return new Promise(async (resolve, reject) => {
|
|
189
231
|
await fsPromises.rm(logFile, { force: true });
|
|
190
|
-
const
|
|
232
|
+
const socket = os.platform() === 'win32' ? `MySQL-${crypto.randomUUID()}` : `${dbPath}/m.sock`;
|
|
233
|
+
const xSocket = os.platform() === 'win32' ? `MySQLX-${crypto.randomUUID()}` : `${dbPath}/x.sock`;
|
|
234
|
+
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
235
|
//resolveFunction is the function that will be called to resolve the promise that stops the database.
|
|
192
236
|
//If resolveFunction is not undefined, the database has received a kill signal and data cleanup procedures should run.
|
|
193
237
|
//Once ran, resolveFunction will be called.
|
|
@@ -206,7 +250,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
206
250
|
if (portIssue || xPortIssue) {
|
|
207
251
|
this.logger.log('Error log when exiting for port in use error:', errorLog);
|
|
208
252
|
try {
|
|
209
|
-
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, options.
|
|
253
|
+
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, options._DO_NOT_USE_dbPath);
|
|
210
254
|
}
|
|
211
255
|
catch (e) {
|
|
212
256
|
this.logger.error(e);
|
|
@@ -215,22 +259,18 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
215
259
|
return reject('Port is already in use');
|
|
216
260
|
}
|
|
217
261
|
try {
|
|
218
|
-
if (options.
|
|
262
|
+
if (options._DO_NOT_USE_deleteDBAfterStopped) {
|
|
219
263
|
await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_deleteDatabaseDirectory).call(this, dbPath);
|
|
220
264
|
}
|
|
221
265
|
}
|
|
222
266
|
catch (e) {
|
|
223
|
-
this.logger.error('An
|
|
267
|
+
this.logger.error('An error occurred while deleting database directory at path:', dbPath, '| The error was:', e);
|
|
224
268
|
}
|
|
225
269
|
finally {
|
|
226
270
|
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 });
|
|
271
|
+
const binaryPathToDelete = __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_returnBinaryPathToDelete).call(this, binaryFilepath, options);
|
|
272
|
+
if (binaryPathToDelete) {
|
|
273
|
+
await fsPromises.rm(binaryPathToDelete, { force: true, recursive: true, maxRetries: 50 });
|
|
234
274
|
}
|
|
235
275
|
}
|
|
236
276
|
catch (e) {
|
|
@@ -282,11 +322,14 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
282
322
|
resolve({
|
|
283
323
|
port,
|
|
284
324
|
xPort: mySQLXPort,
|
|
325
|
+
socket,
|
|
326
|
+
xSocket,
|
|
285
327
|
dbName: options.dbName,
|
|
286
328
|
username: options.username,
|
|
287
329
|
stop: () => {
|
|
288
330
|
return new Promise(async (resolve, reject) => {
|
|
289
331
|
resolveFunction = resolve;
|
|
332
|
+
this.removeExitHandler();
|
|
290
333
|
const killed = await __classPrivateFieldGet(this, _Executor_instances, "m", _Executor_killProcess).call(this, process);
|
|
291
334
|
if (!killed) {
|
|
292
335
|
reject();
|
|
@@ -354,11 +397,11 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
354
397
|
let lockRelease;
|
|
355
398
|
while (true) {
|
|
356
399
|
try {
|
|
357
|
-
lockRelease = (0,
|
|
400
|
+
lockRelease = await (0, FileLock_1.lockFile)(copyPath);
|
|
358
401
|
break;
|
|
359
402
|
}
|
|
360
403
|
catch (e) {
|
|
361
|
-
if (e
|
|
404
|
+
if (e === 'LOCKED') {
|
|
362
405
|
this.logger.log('Waiting for lock for libaio copy');
|
|
363
406
|
await (0, FileLock_1.waitForLock)(copyPath, options);
|
|
364
407
|
this.logger.log('Lock is gone for libaio copy');
|
|
@@ -396,7 +439,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
396
439
|
}
|
|
397
440
|
finally {
|
|
398
441
|
try {
|
|
399
|
-
lockRelease();
|
|
442
|
+
await lockRelease();
|
|
400
443
|
}
|
|
401
444
|
catch (e) {
|
|
402
445
|
this.logger.error('Error unlocking libaio file:', e);
|
|
@@ -427,7 +470,7 @@ _Executor_instances = new WeakSet(), _Executor_executeFile = function _Executor_
|
|
|
427
470
|
initText += `\n${options.initSQLString}`;
|
|
428
471
|
}
|
|
429
472
|
this.logger.log('Writing init file');
|
|
430
|
-
await fsPromises.writeFile(`${options.
|
|
473
|
+
await fsPromises.writeFile(`${options._DO_NOT_USE_dbPath}/init.sql`, initText, { encoding: 'utf8' });
|
|
431
474
|
this.logger.log('Finished writing init file');
|
|
432
475
|
};
|
|
433
476
|
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.0",
|
|
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
|
}
|