import { __awaiter, __rest } from "tslib"; import { createHash } from 'node:crypto'; import fs from 'node:fs'; import module from 'node:module'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { MessageChannel, Worker, parentPort, receiveMessageOnPort, workerData, } from 'node:worker_threads'; import { findUp, isPkgAvailable, tryExtensions } from '@pkgr/core'; const INT32_BYTES = 4; export * from './types.js'; export const TsRunner = { TsNode: 'ts-node', EsbuildRegister: 'esbuild-register', EsbuildRunner: 'esbuild-runner', SWC: 'swc', TSX: 'tsx', }; const { NODE_OPTIONS, SYNCKIT_EXEC_ARGV, SYNCKIT_GLOBAL_SHIMS, SYNCKIT_TIMEOUT, SYNCKIT_TS_RUNNER, } = process.env; const IS_NODE_20 = Number(process.versions.node.split('.')[0]) >= 20; export const DEFAULT_TIMEOUT = SYNCKIT_TIMEOUT ? +SYNCKIT_TIMEOUT : undefined; export const DEFAULT_EXEC_ARGV = (SYNCKIT_EXEC_ARGV === null || SYNCKIT_EXEC_ARGV === void 0 ? void 0 : SYNCKIT_EXEC_ARGV.split(',')) || []; export const DEFAULT_TS_RUNNER = SYNCKIT_TS_RUNNER; export const DEFAULT_GLOBAL_SHIMS = ['1', 'true'].includes(SYNCKIT_GLOBAL_SHIMS); export const DEFAULT_GLOBAL_SHIMS_PRESET = [ { moduleName: 'node-fetch', globalName: 'fetch', }, { moduleName: 'node:perf_hooks', globalName: 'performance', named: 'performance', }, ]; export const MTS_SUPPORTED_NODE_VERSION = 16; let syncFnCache; export function extractProperties(object) { if (object && typeof object === 'object') { const properties = {}; for (const key in object) { properties[key] = object[key]; } return properties; } } export function createSyncFn(workerPath, timeoutOrOptions) { syncFnCache !== null && syncFnCache !== void 0 ? syncFnCache : (syncFnCache = new Map()); const cachedSyncFn = syncFnCache.get(workerPath); if (cachedSyncFn) { return cachedSyncFn; } if (!path.isAbsolute(workerPath)) { throw new Error('`workerPath` must be absolute'); } const syncFn = startWorkerThread(workerPath, typeof timeoutOrOptions === 'number' ? { timeout: timeoutOrOptions } : timeoutOrOptions); syncFnCache.set(workerPath, syncFn); return syncFn; } const cjsRequire = typeof require === 'undefined' ? module.createRequire(import.meta.url) : require; const dataUrl = (code) => new URL(`data:text/javascript,${encodeURIComponent(code)}`); export const isFile = (path) => { var _a; try { return !!((_a = fs.statSync(path, { throwIfNoEntry: false })) === null || _a === void 0 ? void 0 : _a.isFile()); } catch (_b) { return false; } }; const setupTsRunner = (workerPath, { execArgv, tsRunner }) => { let ext = path.extname(workerPath); if (!/[/\\]node_modules[/\\]/.test(workerPath) && (!ext || /^\.[cm]?js$/.test(ext))) { const workPathWithoutExt = ext ? workerPath.slice(0, -ext.length) : workerPath; let extensions; switch (ext) { case '.cjs': { extensions = ['.cts', '.cjs']; break; } case '.mjs': { extensions = ['.mts', '.mjs']; break; } default: { extensions = ['.ts', '.js']; break; } } const found = tryExtensions(workPathWithoutExt, extensions); let differentExt; if (found && (!ext || (differentExt = found !== workPathWithoutExt))) { workerPath = found; if (differentExt) { ext = path.extname(workerPath); } } } const isTs = /\.[cm]?ts$/.test(workerPath); let jsUseEsm = workerPath.endsWith('.mjs'); let tsUseEsm = workerPath.endsWith('.mts'); if (isTs) { if (!tsUseEsm) { const pkg = findUp(workerPath); if (pkg) { tsUseEsm = cjsRequire(pkg).type === 'module'; } } if (tsRunner == null && isPkgAvailable(TsRunner.TsNode)) { tsRunner = TsRunner.TsNode; } switch (tsRunner) { case TsRunner.TsNode: { if (tsUseEsm) { if (!execArgv.includes('--loader')) { execArgv = ['--loader', `${TsRunner.TsNode}/esm`, ...execArgv]; } } else if (!execArgv.includes('-r')) { execArgv = ['-r', `${TsRunner.TsNode}/register`, ...execArgv]; } break; } case TsRunner.EsbuildRegister: { if (!execArgv.includes('-r')) { execArgv = ['-r', TsRunner.EsbuildRegister, ...execArgv]; } break; } case TsRunner.EsbuildRunner: { if (!execArgv.includes('-r')) { execArgv = ['-r', `${TsRunner.EsbuildRunner}/register`, ...execArgv]; } break; } case TsRunner.SWC: { if (!execArgv.includes('-r')) { execArgv = ['-r', `@${TsRunner.SWC}-node/register`, ...execArgv]; } break; } case TsRunner.TSX: { if (!execArgv.includes('--loader')) { execArgv = ['--loader', TsRunner.TSX, ...execArgv]; } break; } default: { throw new Error(`Unknown ts runner: ${String(tsRunner)}`); } } } else if (!jsUseEsm) { const pkg = findUp(workerPath); if (pkg) { jsUseEsm = cjsRequire(pkg).type === 'module'; } } let resolvedPnpLoaderPath; if (process.versions.pnp) { const nodeOptions = NODE_OPTIONS === null || NODE_OPTIONS === void 0 ? void 0 : NODE_OPTIONS.split(/\s+/); let pnpApiPath; try { pnpApiPath = cjsRequire.resolve('pnpapi'); } catch (_a) { } if (pnpApiPath && !(nodeOptions === null || nodeOptions === void 0 ? void 0 : nodeOptions.some((option, index) => ['-r', '--require'].includes(option) && pnpApiPath === cjsRequire.resolve(nodeOptions[index + 1]))) && !execArgv.includes(pnpApiPath)) { execArgv = ['-r', pnpApiPath, ...execArgv]; const pnpLoaderPath = path.resolve(pnpApiPath, '../.pnp.loader.mjs'); if (isFile(pnpLoaderPath)) { resolvedPnpLoaderPath = pathToFileURL(pnpLoaderPath).toString(); if (!IS_NODE_20) { execArgv = [ '--experimental-loader', resolvedPnpLoaderPath, ...execArgv, ]; } } } } return { ext, isTs, jsUseEsm, tsRunner, tsUseEsm, workerPath, pnpLoaderPath: resolvedPnpLoaderPath, execArgv, }; }; const md5Hash = (text) => createHash('md5').update(text).digest('hex'); export const encodeImportModule = (moduleNameOrGlobalShim, type = 'import') => { const { moduleName, globalName, named, conditional } = typeof moduleNameOrGlobalShim === 'string' ? { moduleName: moduleNameOrGlobalShim } : moduleNameOrGlobalShim; const importStatement = type === 'import' ? `import${globalName ? ' ' + (named === null ? '* as ' + globalName : (named === null || named === void 0 ? void 0 : named.trim()) ? `{${named}}` : globalName) + ' from' : ''} '${path.isAbsolute(moduleName) ? String(pathToFileURL(moduleName)) : moduleName}'` : `${globalName ? 'const ' + ((named === null || named === void 0 ? void 0 : named.trim()) ? `{${named}}` : globalName) + '=' : ''}require('${moduleName .replace(/\\/g, '\\\\')}')`; if (!globalName) { return importStatement; } const overrideStatement = `globalThis.${globalName}=${(named === null || named === void 0 ? void 0 : named.trim()) ? named : globalName}`; return (importStatement + (conditional === false ? `;${overrideStatement}` : `;if(!globalThis.${globalName})${overrideStatement}`)); }; export const _generateGlobals = (globalShims, type) => globalShims.reduce((acc, shim) => `${acc}${acc ? ';' : ''}${encodeImportModule(shim, type)}`, ''); let globalsCache; let tmpdir; const _dirname = typeof __dirname === 'undefined' ? path.dirname(fileURLToPath(import.meta.url)) : __dirname; let sharedBuffer; let sharedBufferView; export const generateGlobals = (workerPath, globalShims, type = 'import') => { globalsCache !== null && globalsCache !== void 0 ? globalsCache : (globalsCache = new Map()); const cached = globalsCache.get(workerPath); if (cached) { const [content, filepath] = cached; if ((type === 'require' && !filepath) || (type === 'import' && filepath && isFile(filepath))) { return content; } } const globals = _generateGlobals(globalShims, type); let content = globals; let filepath; if (type === 'import') { if (!tmpdir) { tmpdir = path.resolve(findUp(_dirname), '../node_modules/.synckit'); } fs.mkdirSync(tmpdir, { recursive: true }); filepath = path.resolve(tmpdir, md5Hash(workerPath) + '.mjs'); content = encodeImportModule(filepath); fs.writeFileSync(filepath, globals); } globalsCache.set(workerPath, [content, filepath]); return content; }; function startWorkerThread(workerPath, { timeout = DEFAULT_TIMEOUT, execArgv = DEFAULT_EXEC_ARGV, tsRunner = DEFAULT_TS_RUNNER, transferList = [], globalShims = DEFAULT_GLOBAL_SHIMS, } = {}) { const { port1: mainPort, port2: workerPort } = new MessageChannel(); const { isTs, ext, jsUseEsm, tsUseEsm, tsRunner: finalTsRunner, workerPath: finalWorkerPath, pnpLoaderPath, execArgv: finalExecArgv, } = setupTsRunner(workerPath, { execArgv, tsRunner }); const workerPathUrl = pathToFileURL(finalWorkerPath); if (/\.[cm]ts$/.test(finalWorkerPath)) { const isTsxSupported = !tsUseEsm || Number.parseFloat(process.versions.node) >= MTS_SUPPORTED_NODE_VERSION; if (!finalTsRunner) { throw new Error('No ts runner specified, ts worker path is not supported'); } else if ([ TsRunner.EsbuildRegister, TsRunner.EsbuildRunner, TsRunner.SWC, ...(isTsxSupported ? [] : [TsRunner.TSX]), ].includes(finalTsRunner)) { throw new Error(`${finalTsRunner} is not supported for ${ext} files yet` + (isTsxSupported ? ', you can try [tsx](https://github.com/esbuild-kit/tsx) instead' : '')); } } const finalGlobalShims = (globalShims === true ? DEFAULT_GLOBAL_SHIMS_PRESET : Array.isArray(globalShims) ? globalShims : []).filter(({ moduleName }) => isPkgAvailable(moduleName)); sharedBufferView !== null && sharedBufferView !== void 0 ? sharedBufferView : (sharedBufferView = new Int32Array((sharedBuffer !== null && sharedBuffer !== void 0 ? sharedBuffer : (sharedBuffer = new SharedArrayBuffer(INT32_BYTES))), 0, 1)); const useGlobals = finalGlobalShims.length > 0; const useEval = isTs ? !tsUseEsm : !jsUseEsm && useGlobals; const worker = new Worker((jsUseEsm && useGlobals) || (tsUseEsm && finalTsRunner === TsRunner.TsNode) ? dataUrl(`${generateGlobals(finalWorkerPath, finalGlobalShims)};import '${String(workerPathUrl)}'`) : useEval ? `${generateGlobals(finalWorkerPath, finalGlobalShims, 'require')};${encodeImportModule(finalWorkerPath, 'require')}` : workerPathUrl, { eval: useEval, workerData: { sharedBuffer, workerPort, pnpLoaderPath }, transferList: [workerPort, ...transferList], execArgv: finalExecArgv, }); let nextID = 0; const receiveMessageWithId = (port, expectedId, waitingTimeout) => { const start = Date.now(); const status = Atomics.wait(sharedBufferView, 0, 0, waitingTimeout); Atomics.store(sharedBufferView, 0, 0); if (!['ok', 'not-equal'].includes(status)) { const abortMsg = { id: expectedId, cmd: 'abort', }; port.postMessage(abortMsg); throw new Error('Internal error: Atomics.wait() failed: ' + status); } const _a = receiveMessageOnPort(mainPort).message, { id } = _a, message = __rest(_a, ["id"]); if (id < expectedId) { const waitingTime = Date.now() - start; return receiveMessageWithId(port, expectedId, waitingTimeout ? waitingTimeout - waitingTime : undefined); } if (expectedId !== id) { throw new Error(`Internal error: Expected id ${expectedId} but got id ${id}`); } return Object.assign({ id }, message); }; const syncFn = (...args) => { const id = nextID++; const msg = { id, args }; worker.postMessage(msg); const { result, error, properties } = receiveMessageWithId(mainPort, id, timeout); if (error) { throw Object.assign(error, properties); } return result; }; worker.unref(); return syncFn; } export function runAsWorker(fn) { if (!workerData) { return; } const { workerPort, sharedBuffer, pnpLoaderPath } = workerData; if (pnpLoaderPath && IS_NODE_20) { module.register(pnpLoaderPath); } const sharedBufferView = new Int32Array(sharedBuffer, 0, 1); parentPort.on('message', ({ id, args }) => { ; (() => __awaiter(this, void 0, void 0, function* () { let isAborted = false; const handleAbortMessage = (msg) => { if (msg.id === id && msg.cmd === 'abort') { isAborted = true; } }; workerPort.on('message', handleAbortMessage); let msg; try { msg = { id, result: yield fn(...args) }; } catch (error) { msg = { id, error, properties: extractProperties(error) }; } workerPort.off('message', handleAbortMessage); if (isAborted) { return; } workerPort.postMessage(msg); Atomics.add(sharedBufferView, 0, 1); Atomics.notify(sharedBufferView, 0); }))(); }); } //# sourceMappingURL=index.js.map