worker-parrot-party
Version:
Parrot party for everyone
188 lines (186 loc) • 8.23 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScriptManager = void 0;
/* eslint-disable no-await-in-loop */
/* eslint-disable class-methods-use-this */
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const tsc = __importStar(require("tsc-prog"));
var FILE_EXT;
(function (FILE_EXT) {
FILE_EXT["TS"] = ".ts";
FILE_EXT["JS"] = ".js";
})(FILE_EXT || (FILE_EXT = {}));
/**
* Class in charge of managing the worker scripts. It is target to create the TS file to use for the workers and
* compile it, since Workers do not support Typescript files.
*/
class ScriptManager {
constructor(_outDir, _basePath) {
this._outDir = _outDir;
this._basePath = _basePath;
}
/**
* This function will compile the typescript file and create a plain JS file, that will be
* used as a worker script.
* @param fileName Name of the file to compile
* @param filePath Path where the file is located
*/
_compileScriptFile(fileName, folderPath) {
const fullFilePath = path.join(folderPath, fileName);
tsc.build({
basePath: this._basePath,
// REVISIT: Allow to specify the tsconfig file
// configFilePath: path.join(__dirname, 'tsconfig.json'),
compilerOptions: {
module: 'commonjs',
moduleResolution: 'node',
pretty: true,
sourceMap: true,
baseUrl: this._basePath,
outDir: this._outDir,
declaration: true,
skipDefaultLibCheck: true,
esModuleInterop: true,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
},
include: [fullFilePath],
exclude: ['**/*.test.ts', '**/*.spec.ts'],
});
}
/**
* This function will delete the previous script file, if it exists.
* We do this in order to ensure that we only have a single execution script per pool of workers.
* @param filePath Path to the script file
*/
_deletePreviosScript(filePath) {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}
/**
* Function to determine the name of the task function.
* @param task
* @returns task name if exists otherwise returns 'anonymous'
*/
_determineTaskFunctionName(task) {
return task.name || 'anonymous';
}
/**
* This function transforms a given task function into a worker script.
*
* NOTE: In order to avoid compilation error warnings due to nature of typescipt, we use the flag
* @ts-nocheck to disable the type checking.
*
* reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html
* @param task
* @returns String representation of the task to be used by the worker functions
*/
_createWorkerScriptString(task, handlers, libs) {
const stringifedFunction = task.toString();
const taskName = this._determineTaskFunctionName(task);
const handlerFunctions = handlers?.reduce((acc, handler) => acc.concat(handler.toString(), '\n'), '');
const libsString = libs?.reduce((acc, lib) => {
return lib?.importDeclaration == null || lib?.importDeclaration === ''
? acc.concat(`import '${lib?.name}'\n`)
: acc.concat(`import ${lib?.importDeclaration} from '${lib?.name}'\n`);
}, '');
return `
// @ts-nocheck
import { parentPort } from 'worker_threads'
${libsString != null || libsString?.length > 0 ? libsString : ''}
${handlerFunctions != null || handlerFunctions?.length > 0 ? handlerFunctions : ''}
${stringifedFunction}
parentPort.on('message', async data => {
try {
const result = await ${taskName}(...data.args)
parentPort.postMessage({ data: result })
} catch(error) {
// Unexpected error happen
console.error(error)
}
})
`;
}
/**
* This function converts a task function into a TS file, that will be used as a worker script. In addition,
* deletes the previously created TS file and compiles a new version to be used by the worker threads.
*
* NOTE: When calling the function toString() function, we receieve a JS representation of the function that uses
* ___awaitor.
*
* References:
* - https://medium.com/@joshuakgoldberg/hacking-typescripts-async-await-awaiter-for-jquery-2-s-promises-60612e293c4b
* - https://github.com/SUCHMOKUO/node-worker-threads-pool/blob/f2220030a27e5b8333e12fb9edafeb29602a484b/src/utils.ts
*
* @param task
* @returns full qualified path of the JS worker script.
*/
async createAndCompileExecutionScript(task, handlers, libs) {
// REVISIT: We should verify that the task is in fact a promise
const executionScriptContent = this._createWorkerScriptString(task, handlers, libs);
const taskName = this._determineTaskFunctionName(task);
const fileName = `${taskName}.ms-parrot`;
const tsFileName = fileName.concat(FILE_EXT.TS);
const jsFileName = fileName.concat(FILE_EXT.JS);
const workerScriptFolder = path.join(this._basePath, './worker-scripts');
if (!fs.existsSync(workerScriptFolder)) {
fs.mkdirSync(workerScriptFolder);
}
const fullFilePath = path.join(workerScriptFolder, tsFileName);
// NOTE: Ensures that previous script is deleted, to ensure we only have a single execution script per pool of
// workers.
this._deletePreviosScript(fullFilePath);
fs.appendFileSync(fullFilePath, executionScriptContent);
this._compileScriptFile(tsFileName, workerScriptFolder);
return this.getFullPathOfCompiledFile(path.join(this._basePath, this._outDir), jsFileName);
}
async listFolderFiles(srcFolder) {
const entries = await fs.readdirSync(srcFolder, { withFileTypes: true });
let files = entries
.filter((file) => !file.isDirectory())
.map((file) => path.join(srcFolder, file.name));
const folders = entries.filter((folder) => folder.isDirectory());
// eslint-disable-next-line no-plusplus
for (let idx = 0; idx < folders.length; idx++) {
const folder = folders[idx];
const nestedFolderFiles = await this.listFolderFiles(path.join(srcFolder, folder.name));
files = files.concat(nestedFolderFiles);
}
return files;
}
async getFullPathOfCompiledFile(searchDirectory, jsFileName) {
const compiledFolderStructure = await this.listFolderFiles(searchDirectory);
const foundPath = compiledFolderStructure.filter((fileName) => fileName.endsWith(jsFileName));
if (!foundPath) {
throw new Error(`Compiled file not found [${jsFileName}]`);
}
return foundPath.pop();
}
}
exports.ScriptManager = ScriptManager;
//# sourceMappingURL=ScriptManager.js.map