synckit
Version:
Perform async work synchronously in Node.js using `worker_threads`, or `child_process` as fallback, with first-class TypeScript support.
97 lines • 3.82 kB
JavaScript
var _a;
import { __awaiter } from "tslib";
import path from 'path';
import { MessageChannel, Worker, receiveMessageOnPort, workerData, parentPort, } from 'worker_threads';
export * from './types.js';
const { SYNCKIT_BUFFER_SIZE, SYNCKIT_TIMEOUT, SYNCKIT_TS_ESM, SYNCKIT_EXEC_ARV, } = process.env;
const TS_USE_ESM = !!SYNCKIT_TS_ESM && ['1', 'true'].includes(SYNCKIT_TS_ESM);
export const DEFAULT_BUFFER_SIZE = SYNCKIT_BUFFER_SIZE
? +SYNCKIT_BUFFER_SIZE
: undefined;
export const DEFAULT_TIMEOUT = SYNCKIT_TIMEOUT ? +SYNCKIT_TIMEOUT : undefined;
export const DEFAULT_WORKER_BUFFER_SIZE = DEFAULT_BUFFER_SIZE || 1024;
export const DEFAULT_EXEC_ARGV = (_a = SYNCKIT_EXEC_ARV === null || SYNCKIT_EXEC_ARV === void 0 ? void 0 : SYNCKIT_EXEC_ARV.split(',')) !== null && _a !== void 0 ? _a : [];
const syncFnCache = new Map();
export function createSyncFn(workerPath, bufferSizeOrOptions, timeout) {
if (!path.isAbsolute(workerPath)) {
throw new Error('`workerPath` must be absolute');
}
const cachedSyncFn = syncFnCache.get(workerPath);
if (cachedSyncFn) {
return cachedSyncFn;
}
const syncFn = startWorkerThread(workerPath, typeof bufferSizeOrOptions === 'number'
? { bufferSize: bufferSizeOrOptions, timeout }
: bufferSizeOrOptions);
syncFnCache.set(workerPath, syncFn);
return syncFn;
}
const throwError = (msg) => {
throw new Error(msg);
};
function startWorkerThread(workerPath, { bufferSize = DEFAULT_WORKER_BUFFER_SIZE, timeout = DEFAULT_TIMEOUT, execArgv = DEFAULT_EXEC_ARGV, } = {}) {
const { port1: mainPort, port2: workerPort } = new MessageChannel();
const isTs = workerPath.endsWith('.ts');
const worker = new Worker(isTs
? TS_USE_ESM
? throwError('Native esm in `.ts` file is not supported yet, please use `.cjs` instead')
: `require('ts-node/register');require('${workerPath}')`
: workerPath, {
eval: isTs,
workerData: { workerPort },
transferList: [workerPort],
execArgv,
});
let nextID = 0;
const syncFn = (...args) => {
const id = nextID++;
const sharedBuffer = new SharedArrayBuffer(bufferSize);
const sharedBufferView = new Int32Array(sharedBuffer);
const msg = { sharedBuffer, id, args };
worker.postMessage(msg);
const status = Atomics.wait(sharedBufferView, 0, 0, timeout);
/* istanbul ignore if */
if (!['ok', 'not-equal'].includes(status)) {
throw new Error('Internal error: Atomics.wait() failed: ' + status);
}
const { id: id2, result, error, } = receiveMessageOnPort(mainPort).message;
/* istanbul ignore if */
if (id !== id2) {
throw new Error(`Internal error: Expected id ${id} but got id ${id2}`);
}
if (error) {
throw error;
}
return result;
};
worker.unref();
return syncFn;
}
/* istanbul ignore next */
export function runAsWorker(fn) {
if (!workerData) {
return;
}
const { workerPort } = workerData;
parentPort.on('message', ({ sharedBuffer, id, args }) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
;
(() => __awaiter(this, void 0, void 0, function* () {
const sharedBufferView = new Int32Array(sharedBuffer);
let msg;
try {
msg = { id, result: yield fn(...args) };
}
catch (error) {
msg = {
id,
error,
};
}
workerPort.postMessage(msg);
Atomics.add(sharedBufferView, 0, 1);
Atomics.notify(sharedBufferView, 0);
}))();
});
}
//# sourceMappingURL=index.js.map