Skip to content

Commit 9148238

Browse files
build - 1.0.0
1 parent 05e9192 commit 9148238

File tree

17 files changed

+937
-0
lines changed

17 files changed

+937
-0
lines changed

dist/src/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { ServerOptions } from '../types';
2+
export declare function createDB(opts?: ServerOptions): Promise<import("../types").MySQLDB>;

dist/src/index.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use strict";
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
var desc = Object.getOwnPropertyDescriptor(m, k);
5+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6+
desc = { enumerable: true, get: function() { return m[k]; } };
7+
}
8+
Object.defineProperty(o, k2, desc);
9+
}) : (function(o, m, k, k2) {
10+
if (k2 === undefined) k2 = k;
11+
o[k2] = m[k];
12+
}));
13+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14+
Object.defineProperty(o, "default", { enumerable: true, value: v });
15+
}) : function(o, v) {
16+
o["default"] = v;
17+
});
18+
var __importStar = (this && this.__importStar) || function (mod) {
19+
if (mod && mod.__esModule) return mod;
20+
var result = {};
21+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22+
__setModuleDefault(result, mod);
23+
return result;
24+
};
25+
var __importDefault = (this && this.__importDefault) || function (mod) {
26+
return (mod && mod.__esModule) ? mod : { "default": mod };
27+
};
28+
Object.defineProperty(exports, "__esModule", { value: true });
29+
exports.createDB = createDB;
30+
const Logger_1 = __importDefault(require("./libraries/Logger"));
31+
const os = __importStar(require("node:os"));
32+
const Executor_1 = __importDefault(require("./libraries/Executor"));
33+
const semver_1 = require("semver");
34+
const AbortSignal_1 = __importDefault(require("./libraries/AbortSignal"));
35+
const Version_1 = __importDefault(require("./libraries/Version"));
36+
const versions_json_1 = __importDefault(require("./versions.json"));
37+
const Downloader_1 = require("./libraries/Downloader");
38+
const defaultOptions = {
39+
dbName: 'dbdata',
40+
logLevel: 'ERROR',
41+
portRetries: 10,
42+
downloadBinaryOnce: true,
43+
lockRetries: 1000,
44+
lockRetryWait: 1000
45+
};
46+
process.on('exit', () => {
47+
AbortSignal_1.default.abort('Process is exiting');
48+
});
49+
async function createDB(opts = defaultOptions) {
50+
const options = { ...defaultOptions, ...opts };
51+
const logger = new Logger_1.default(options.logLevel);
52+
const executor = new Executor_1.default(logger);
53+
const version = await executor.getMySQLVersion(options.version);
54+
logger.log('Version currently installed:', version);
55+
if (version === null || (options.version && !(0, semver_1.satisfies)(version.version, options.version))) {
56+
let binaryInfo;
57+
let binaryFilepath;
58+
try {
59+
binaryInfo = (0, Version_1.default)(versions_json_1.default, options.version);
60+
logger.log('Downloading binary:', binaryInfo.version, 'from URL:', binaryInfo.url);
61+
}
62+
catch (e) {
63+
logger.error(e);
64+
if (options.version) {
65+
throw `A MySQL version ${options.version} binary could not be found that supports your OS (${os.platform()} | ${os.version()}) and CPU architecture (${os.arch()}). Please check you have the latest version of mysql-memory-server. If the latest version still doesn't support the version you want to use, feel free to make a pull request to add support!`;
66+
}
67+
throw `A MySQL binary could not be found that supports your OS (${os.platform()} | ${os.version()}) and CPU architecture (${os.arch()}). Please check you have the latest version of mysql-memory-server. If the latest version still doesn't support your OS and CPU architecture, feel free to make a pull request to add support!`;
68+
}
69+
try {
70+
binaryFilepath = await (0, Downloader_1.downloadBinary)(binaryInfo, options, logger);
71+
}
72+
catch (error) {
73+
logger.error('Failed to download binary');
74+
throw error;
75+
}
76+
logger.log('Running downloaded binary');
77+
return await executor.startMySQL(options, binaryFilepath);
78+
}
79+
else {
80+
logger.log(version);
81+
return await executor.startMySQL(options, version.path);
82+
}
83+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
declare const DBDestroySignal: AbortController;
2+
export default DBDestroySignal;

dist/src/libraries/AbortSignal.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const DBDestroySignal = new AbortController();
4+
exports.default = DBDestroySignal;

dist/src/libraries/Downloader.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Logger from './Logger';
2+
import { BinaryInfo, ServerOptions } from '../../types';
3+
export declare function downloadVersions(): Promise<string>;
4+
export declare function downloadBinary(binaryInfo: BinaryInfo, options: ServerOptions, logger: Logger): Promise<string>;

dist/src/libraries/Downloader.js

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
"use strict";
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
var desc = Object.getOwnPropertyDescriptor(m, k);
5+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6+
desc = { enumerable: true, get: function() { return m[k]; } };
7+
}
8+
Object.defineProperty(o, k2, desc);
9+
}) : (function(o, m, k, k2) {
10+
if (k2 === undefined) k2 = k;
11+
o[k2] = m[k];
12+
}));
13+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14+
Object.defineProperty(o, "default", { enumerable: true, value: v });
15+
}) : function(o, v) {
16+
o["default"] = v;
17+
});
18+
var __importStar = (this && this.__importStar) || function (mod) {
19+
if (mod && mod.__esModule) return mod;
20+
var result = {};
21+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22+
__setModuleDefault(result, mod);
23+
return result;
24+
};
25+
var __importDefault = (this && this.__importDefault) || function (mod) {
26+
return (mod && mod.__esModule) ? mod : { "default": mod };
27+
};
28+
Object.defineProperty(exports, "__esModule", { value: true });
29+
exports.downloadVersions = downloadVersions;
30+
exports.downloadBinary = downloadBinary;
31+
const https = __importStar(require("https"));
32+
const fs = __importStar(require("fs"));
33+
const fsPromises = __importStar(require("fs/promises"));
34+
const os = __importStar(require("os"));
35+
const adm_zip_1 = __importDefault(require("adm-zip"));
36+
const path_1 = require("path");
37+
const crypto_1 = require("crypto");
38+
const child_process_1 = require("child_process");
39+
const proper_lockfile_1 = require("proper-lockfile");
40+
function getZipData(entry) {
41+
return new Promise((resolve, reject) => {
42+
entry.getDataAsync((data, err) => {
43+
if (err) {
44+
reject(err);
45+
}
46+
else {
47+
resolve(data);
48+
}
49+
});
50+
});
51+
}
52+
function handleTarExtraction(filepath, extractedPath) {
53+
return new Promise((resolve, reject) => {
54+
(0, child_process_1.exec)(`tar -xf ${filepath} -C ${extractedPath}`, (error, stdout, stderr) => {
55+
if (error || stderr) {
56+
return reject(error || stderr);
57+
}
58+
resolve();
59+
});
60+
});
61+
}
62+
function downloadVersions() {
63+
return new Promise((resolve, reject) => {
64+
let json = "";
65+
https.get("https://github.com/Sebastian-Webster/mysql-memory-server-nodejs/raw/main/versions.json", function (response) {
66+
response
67+
.on("data", append => json += append)
68+
.on("error", e => {
69+
reject(e);
70+
})
71+
.on("end", () => {
72+
resolve(json);
73+
});
74+
});
75+
});
76+
}
77+
function downloadFromCDN(url, downloadLocation, logger) {
78+
return new Promise((resolve, reject) => {
79+
const fileStream = fs.createWriteStream(downloadLocation);
80+
fileStream.on('open', () => {
81+
const request = https.get(url, (response) => {
82+
response.pipe(fileStream);
83+
});
84+
request.on('error', (err) => {
85+
logger.error(err);
86+
fileStream.close();
87+
fs.unlink(downloadLocation, (err) => {
88+
reject(err);
89+
});
90+
});
91+
});
92+
fileStream.on('finish', () => {
93+
resolve();
94+
});
95+
fileStream.on('error', (err) => {
96+
logger.error(err);
97+
fileStream.end();
98+
fs.unlink(downloadLocation, () => {
99+
reject(err);
100+
});
101+
});
102+
});
103+
}
104+
function extractBinary(url, archiveLocation, extractedLocation) {
105+
return new Promise(async (resolve, reject) => {
106+
const lastDashIndex = url.lastIndexOf('-');
107+
const fileExtension = url.slice(lastDashIndex).split('.').splice(1).join('.');
108+
await fsPromises.mkdir(extractedLocation, { recursive: true });
109+
const folderName = url.split('/').at(-1).replace(`.${fileExtension}`, '');
110+
if (fileExtension === 'zip') {
111+
//Only Windows MySQL files use the .zip extension
112+
const zip = new adm_zip_1.default(archiveLocation);
113+
const entries = zip.getEntries();
114+
for (const entry of entries) {
115+
if (entry.isDirectory) {
116+
if (entry.name === folderName) {
117+
await fsPromises.mkdir(`${extractedLocation}/mysql`, { recursive: true });
118+
}
119+
else {
120+
await fsPromises.mkdir(`${extractedLocation}/${entry.entryName}`, { recursive: true });
121+
}
122+
}
123+
else {
124+
const data = await getZipData(entry);
125+
await fsPromises.writeFile(`${extractedLocation}/${entry.entryName}`, data);
126+
}
127+
}
128+
try {
129+
await fsPromises.rm(archiveLocation);
130+
}
131+
finally {
132+
fsPromises.rename(`${extractedLocation}/${folderName}`, `${extractedLocation}/mysql`);
133+
return resolve((0, path_1.normalize)(`${extractedLocation}/mysql/bin/mysqld.exe`));
134+
}
135+
}
136+
handleTarExtraction(archiveLocation, extractedLocation).then(async () => {
137+
try {
138+
await fsPromises.rm(archiveLocation);
139+
}
140+
finally {
141+
fsPromises.rename(`${extractedLocation}/${folderName}`, `${extractedLocation}/mysql`);
142+
resolve(`${extractedLocation}/mysql/bin/mysqld`);
143+
}
144+
}).catch(error => {
145+
reject(`An error occurred while extracting the tar file. Please make sure tar is installed and there is enough storage space for the extraction. The error was: ${error}`);
146+
});
147+
});
148+
}
149+
function waitForLock(path, options) {
150+
return new Promise(async (resolve, reject) => {
151+
let retries = 0;
152+
while (retries <= options.lockRetries) {
153+
retries++;
154+
try {
155+
const locked = (0, proper_lockfile_1.checkSync)(path);
156+
if (!locked) {
157+
return resolve();
158+
}
159+
else {
160+
await new Promise(resolve => setTimeout(resolve, options.lockRetryWait));
161+
}
162+
}
163+
catch (e) {
164+
return reject(e);
165+
}
166+
}
167+
reject(`lockRetries has been exceeded. Lock had not been released after ${options.lockRetryWait} * ${options.lockRetries} milliseconds.`);
168+
});
169+
}
170+
function downloadBinary(binaryInfo, options, logger) {
171+
return new Promise(async (resolve, reject) => {
172+
const { url, version } = binaryInfo;
173+
const dirpath = `${os.tmpdir()}/mysqlmsn/binaries`;
174+
logger.log('Binary path:', dirpath);
175+
await fsPromises.mkdir(dirpath, { recursive: true });
176+
const lastDashIndex = url.lastIndexOf('-');
177+
const fileExtension = url.slice(lastDashIndex).split('.').splice(1).join('.');
178+
if (options.downloadBinaryOnce) {
179+
const extractedPath = `${dirpath}/${version}`;
180+
await fsPromises.mkdir(extractedPath, { recursive: true });
181+
const binaryPath = (0, path_1.normalize)(`${extractedPath}/mysql/bin/mysqld${process.platform === 'win32' ? '.exe' : ''}`);
182+
const binaryExists = fs.existsSync(binaryPath);
183+
if (binaryExists) {
184+
return resolve(binaryPath);
185+
}
186+
try {
187+
(0, proper_lockfile_1.lockSync)(extractedPath);
188+
const archivePath = `${dirpath}/${version}.${fileExtension}`;
189+
await downloadFromCDN(url, archivePath, logger);
190+
await extractBinary(url, archivePath, extractedPath);
191+
try {
192+
(0, proper_lockfile_1.unlockSync)(extractedPath);
193+
}
194+
catch (e) {
195+
return reject(e);
196+
}
197+
return resolve(binaryPath);
198+
}
199+
catch (e) {
200+
if (String(e) === 'Error: Lock file is already being held') {
201+
logger.log('Waiting for lock for MySQL version', version);
202+
await waitForLock(extractedPath, options);
203+
logger.log('Lock is gone for version', version);
204+
return resolve(binaryPath);
205+
}
206+
return reject(e);
207+
}
208+
}
209+
const uuid = (0, crypto_1.randomUUID)();
210+
const zipFilepath = `${dirpath}/${uuid}.${fileExtension}`;
211+
logger.log('Binary filepath:', zipFilepath);
212+
const extractedPath = `${dirpath}/${uuid}`;
213+
try {
214+
await downloadFromCDN(url, zipFilepath, logger);
215+
}
216+
catch (e) {
217+
reject(e);
218+
}
219+
try {
220+
const binaryPath = await extractBinary(url, zipFilepath, extractedPath);
221+
resolve(binaryPath);
222+
}
223+
catch (e) {
224+
reject(e);
225+
}
226+
});
227+
}

dist/src/libraries/Executor.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Logger from "./Logger";
2+
import { InstalledMySQLVersion, InternalServerOptions, MySQLDB } from "../../types";
3+
declare class Executor {
4+
#private;
5+
logger: Logger;
6+
constructor(logger: Logger);
7+
getMySQLVersion(preferredVersion?: string): Promise<InstalledMySQLVersion | null>;
8+
startMySQL(options: InternalServerOptions, binaryFilepath: string): Promise<MySQLDB>;
9+
}
10+
export default Executor;

0 commit comments

Comments
 (0)