UNPKG

make-synchronized

Version:

[![Coverage][codecov_badge]][codecov_link] [![Npm Version][package_version_badge]][package_link] [![MIT License][license_badge]][license_link]

879 lines (852 loc) 26.2 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // source/index.js var index_exports = {}; __export(index_exports, { default: () => makeSynchronized, makeDefaultExportSynchronized: () => makeDefaultExportSynchronized, makeInlineFunctionSynchronized: () => makeInlineFunctionSynchronized, makeModuleSynchronized: () => makeModuleSynchronized, makeSynchronized: () => makeSynchronized, makeSynchronizedFunction: () => makeSynchronizedFunction, makeSynchronizedFunctions: () => makeSynchronizedFunctions }); module.exports = __toCommonJS(index_exports); var import_node_module = __toESM(require("node:module"), 1); // source/constants.js var import_node_worker_threads = require("node:worker_threads"); var IS_SERVER = !import_node_worker_threads.isMainThread && Boolean(import_node_worker_threads.workerData?.isServer); var IS_PRODUCTION = true; var STDIO_STREAMS = ["stdout", "stderr"]; var GLOBAL_SERVER_PROPERTY = "__make-synchronized-server__"; var PING_ACTION_RESPONSE = "pong"; var WORKER_ACTION__APPLY = 1; var WORKER_ACTION__GET = 2; var WORKER_ACTION__OWN_KEYS = 3; var WORKER_ACTION__GET_INFORMATION = 4; var WORKER_ACTION__PING = 5; var VALUE_TYPE__FUNCTION = 1; var VALUE_TYPE__PRIMITIVE = 2; var VALUE_TYPE__PLAIN_OBJECT = 3; var VALUE_TYPE__UNKNOWN = 4; var VALUE_INFORMATION__FUNCTION = { type: VALUE_TYPE__FUNCTION }; var ATOMICS_WAIT_RESULT__TIMED_OUT = "timed-out"; var RESPONSE_TYPE__REJECT = 2; var RESPONSE_TYPE__TERMINATE = 3; var MODULE_TYPE__INLINE_FUNCTION = 1; // source/server.js var import_node_worker_threads3 = require("node:worker_threads"); // source/load-module.js var import_node_worker_threads2 = require("node:worker_threads"); var moduleImportPromise; var moduleInstance; var moduleLoadError; async function loadModule() { if (moduleInstance) { return moduleInstance; } if (moduleLoadError) { throw moduleLoadError; } try { moduleInstance = await moduleImportPromise; } catch (error) { moduleLoadError = error; throw error; } return moduleImportPromise; } var initializeModule = async () => { if (import_node_worker_threads2.workerData.exposeSetModuleInstance) { Object.defineProperty(globalThis, GLOBAL_SERVER_PROPERTY, { enumerable: false, configurable: true, writable: false, value: { setModuleInstance(module3) { delete globalThis[GLOBAL_SERVER_PROPERTY]; if (!IS_PRODUCTION && Object.getOwnPropertyDescriptor( globalThis, GLOBAL_SERVER_PROPERTY ) !== void 0) { throw new Error("Unexpected error."); } moduleInstance = module3; } } }); return; } moduleImportPromise = import(import_node_worker_threads2.workerData.module.source); try { moduleInstance = await moduleImportPromise; } catch (error) { moduleLoadError = error; } }; var load_module_default = loadModule; // source/responser.js var import_node_process2 = __toESM(require("node:process"), 1); var import_node_util = __toESM(require("node:util"), 1); // source/data-clone-error.js function isDataCloneError(error) { return error instanceof DOMException && error.name === "DataCloneError"; } // source/atomics-wait-error.js var AtomicsWaitError = class extends Error { name = "AtomicsWaitError"; constructor(code, { semaphore, expected }) { super( code === ATOMICS_WAIT_RESULT__TIMED_OUT ? "Timed out" : "Unexpected error" ); Object.assign(this, { code, semaphore, expected }); } }; var atomics_wait_error_default = AtomicsWaitError; // source/lock.js var SIGNAL_INDEX = 0; var unlock = (semaphore) => { Atomics.add(semaphore, SIGNAL_INDEX, 1); Atomics.notify(semaphore, SIGNAL_INDEX, 1); }; var Lock = class { semaphore = new Int32Array( new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT) ); #messageCount = 0; lock(timeout) { const { semaphore } = this; const previous = this.#messageCount; while (true) { this.#messageCount = Atomics.load(semaphore, SIGNAL_INDEX); if (this.#messageCount > previous) { return; } const result = Atomics.wait(semaphore, SIGNAL_INDEX, previous, timeout); if (result === ATOMICS_WAIT_RESULT__TIMED_OUT) { throw new atomics_wait_error_default(result, { semaphore, expected: previous }); } } } }; var lock_default = Lock; // source/get-value-information.js var PRIMITIVE_VALUE_TYPES = /* @__PURE__ */ new Set([ "undefined", "boolean", "number", "bigint", "string" ]); var isPrimitive = (value) => value === null || PRIMITIVE_VALUE_TYPES.has(typeof value); function getPlainObjectPropertyInformation(object, key) { const descriptor = Object.getOwnPropertyDescriptor(object, key); if (!Object.hasOwn(descriptor, "value")) { return; } const { value } = descriptor; if (isPrimitive(value)) { return { type: VALUE_TYPE__PRIMITIVE, value }; } } function getValueInformation(value) { if (typeof value === "function") { return VALUE_INFORMATION__FUNCTION; } if (isPrimitive(value)) { return { type: VALUE_TYPE__PRIMITIVE, value }; } const information = { type: VALUE_TYPE__UNKNOWN }; if (Object.getPrototypeOf(value) === null) { information.type = VALUE_TYPE__PLAIN_OBJECT; information.isNullPrototypeObject = true; } if (value.constructor === Object) { information.type = VALUE_TYPE__PLAIN_OBJECT; } if (information.type === VALUE_TYPE__PLAIN_OBJECT) { information.properties = new Map( Object.keys(value).map((property) => [ property, getPlainObjectPropertyInformation(value, property) ]) ); } return information; } var get_value_information_default = getValueInformation; // source/property-path.js var normalizePath = (propertyOrPath = []) => Array.isArray(propertyOrPath) ? propertyOrPath : [propertyOrPath]; var hashPath = (path2) => JSON.stringify(normalizePath(path2)); // source/process-action.js function getValue(value, payload) { let receiver; for (const property of normalizePath(payload.path)) { receiver = value; value = Reflect.get(value, property, value); } return { value, receiver }; } async function processAction(action, payload) { if (action === WORKER_ACTION__PING) { return PING_ACTION_RESPONSE; } const module3 = await load_module_default(); const result = getValue(module3, payload); switch (action) { case WORKER_ACTION__GET: return result.value; case WORKER_ACTION__APPLY: return Reflect.apply(result.value, result.receiver, payload.argumentsList); case WORKER_ACTION__OWN_KEYS: return Reflect.ownKeys(result.value).filter( (key) => typeof key !== "symbol" ); case WORKER_ACTION__GET_INFORMATION: return get_value_information_default(result.value); } throw new Error(`Unknown action '${action}'.`); } var process_action_default = processAction; // source/response-message.js var import_node_process = __toESM(require("node:process"), 1); function packRejectedValue(value) { if (value instanceof Error) { return { error: value, errorData: { ...value } }; } return { error: value }; } function unpackRejectedValue({ error, errorData }) { if (error instanceof Error && errorData) { return Object.assign(error, errorData); } return error; } function packResponseMessage(stdio, data, type) { let extraData; if (stdio.length !== 0) { extraData ??= {}; extraData.stdio = stdio; } const { exitCode } = import_node_process.default; if (typeof exitCode === "number" && exitCode !== 0) { extraData ??= {}; extraData.exitCode = exitCode; } if (type === RESPONSE_TYPE__TERMINATE) { extraData ??= {}; extraData.terminated = true; } if (type === RESPONSE_TYPE__REJECT) { extraData ??= {}; extraData.rejected = true; return [packRejectedValue(data), extraData]; } const message = [data]; if (extraData !== void 0) { message.push(extraData); } return message; } function unpackResponseMessage(message) { if (message.length === 1) { return { result: message[0] }; } const [data, extraData] = message; if (extraData.rejected) { extraData.error = unpackRejectedValue(data); } return { result: data, ...extraData }; } // source/responser.js var originalProcessExit = import_node_process2.default.exit; var Responser = class { #channel; #stdio = []; constructor(channel) { this.#channel = channel; import_node_process2.default.exit = (exitCode) => { import_node_process2.default.exitCode = exitCode; this.#terminate(); originalProcessExit(exitCode); }; for (const stream of STDIO_STREAMS) { import_node_process2.default[stream]._writev = (chunks, callback) => { for (const { chunk } of chunks) { this.#stdio.push({ stream, chunk }); } callback(); }; } } #send(data, type) { const { responsePort } = this.#channel; const stdio = this.#stdio; const message = packResponseMessage(stdio, data, type); try { responsePort.postMessage(message); } catch (postMessageError) { const error = isDataCloneError(postMessageError) ? new Error( `Cannot serialize worker response: ${import_node_util.default.inspect(data)}`, { cause: postMessageError } ) : postMessageError; responsePort.postMessage( packResponseMessage(stdio, error, RESPONSE_TYPE__REJECT) ); } finally { this.#finish(); } } #resolve(result) { this.#send(result); } #reject(error) { this.#send(error, RESPONSE_TYPE__REJECT); } #finish() { unlock(this.#channel.responseSemaphore); import_node_process2.default.exitCode = void 0; this.#stdio.length = 0; } #terminate() { this.#send(void 0, RESPONSE_TYPE__TERMINATE); } async process({ action, payload }) { try { this.#resolve(await process_action_default(action, payload)); } catch (error) { this.#reject(error); } } destroy() { this.#channel.responsePort.close(); import_node_process2.default.exitCode = void 0; } }; var responser_default = Responser; // source/server.js function startServer() { let responser; import_node_worker_threads3.parentPort.on("message", ([action, payload, channel]) => { if (channel) { responser?.destroy(); responser = new responser_default(channel); } responser.process({ action, payload }); }); try { initializeModule(); } catch { } } var server_default = startServer; // source/threads-worker.js var import_node_process3 = __toESM(require("node:process"), 1); var import_node_url = require("node:url"); var util2 = __toESM(require("node:util"), 1); var import_node_worker_threads5 = require("node:worker_threads"); // source/channel.js var import_node_worker_threads4 = require("node:worker_threads"); var Channel = class { mainThreadPort; workerPort; alive = true; #lock = new lock_default(); constructor() { const { port1: mainThreadPort, port2: workerPort } = new import_node_worker_threads4.MessageChannel(); mainThreadPort.unref(); workerPort.unref(); this.mainThreadPort = mainThreadPort; this.workerPort = workerPort; } getResponse(timeout) { try { this.#lock.lock(timeout); } catch (error) { if (error instanceof atomics_wait_error_default) { this.destroy(); } throw error; } const message = this.#receiveMessage(); return unpackResponseMessage(message); } #receiveMessage() { const port = this.mainThreadPort; let lastEntry; while (true) { const entry = (0, import_node_worker_threads4.receiveMessageOnPort)(port); if (!entry) { return lastEntry.message; } lastEntry = entry; } } get semaphore() { return this.#lock.semaphore; } destroy() { if (!this.alive) { return; } this.alive = false; this.mainThreadPort.close(); this.workerPort.close(); this.mainThreadPort = void 0; this.workerPort = void 0; } }; var channel_default = Channel; // source/wait-for-worker.js function waitForWorker(worker, lock, workerFile2) { if (IS_PRODUCTION) { return; } let lockWaitError; try { lock.lock(2e3); } catch (error) { if (error instanceof atomics_wait_error_default) { lockWaitError = error; } else { throw error; } } if (!lockWaitError) { return; } let pingError; try { worker.sendAction(WORKER_ACTION__PING, void 0, 2e3); } catch (error) { if (error instanceof atomics_wait_error_default) { pingError = error; } else { throw error; } } if (!pingError) { return; } if (lockWaitError) { throw new AggregateError( [lockWaitError, pingError], `Unexpected error, most likely caused by syntax error in '${workerFile2}'` ); } } var wait_for_worker_default = waitForWorker; // source/threads-worker.js var shouldUseLegacyEvalMode = () => { const version = import_node_process3.default.versions.node; if (!version) { return true; } const majorVersion = Number(version.split(".")[0]); if (majorVersion < 20) { return true; } if (majorVersion < 22 && import_node_process3.default.platform === "win32") { return true; } return false; }; var workerFile; var setWorkFile = (file) => { workerFile = file; }; var WORKER_OPTIONS = { // https://nodejs.org/api/worker_threads.html#new-workerfilename-options // Do not pipe `stdio`s stdout: true, stderr: true, trackUnmanagedFds: false }; if (globalThis.Bun) { delete WORKER_OPTIONS.stdout; delete WORKER_OPTIONS.stderr; } var ThreadsWorker = class { #worker; #module; #channel; #workerIsAlive; #workerOnlineLock; constructor(module3) { this.#module = module3; } #createWorker() { const module3 = this.#module; const workerData3 = { isServer: true }; const workerOptions = { workerData: workerData3, ...WORKER_OPTIONS }; let lock; if (!IS_PRODUCTION) { lock = new lock_default(); workerOptions.workerData.workerRunningSemaphore = lock.semaphore; } let worker; if (module3.type === MODULE_TYPE__INLINE_FUNCTION) { workerData3.exposeSetModuleInstance = true; workerOptions.eval = true; const workUrl = workerFile instanceof URL ? workerFile : (0, import_node_url.pathToFileURL)(workerFile); const setModuleInstance = ( /* Indent */ ` globalThis[${JSON.stringify(GLOBAL_SERVER_PROPERTY)}] .setModuleInstance({default: ${module3.code}}) ` ); worker = new import_node_worker_threads5.Worker( shouldUseLegacyEvalMode() ? ( /* Indent */ ` import(${JSON.stringify(workUrl)}) .then(() => { ${setModuleInstance} }) ` ) : ( /* Indent */ ` import ${JSON.stringify(workUrl)} ${setModuleInstance} ` ), workerOptions ); } else { workerData3.module = module3; worker = new import_node_worker_threads5.Worker(workerFile, workerOptions); } worker.unref(); this.#workerIsAlive = false; this.#workerOnlineLock = lock; return worker; } #killWorker(worker) { if (this.#worker !== worker) { return; } this.#worker = void 0; this.#workerIsAlive = false; this.#workerOnlineLock = void 0; } #createChannel() { if (this.#channel?.alive) { return false; } this.#channel = new channel_default(); return true; } sendAction(action, payload, timeout) { this.#worker ??= this.#createWorker(); if (!IS_PRODUCTION && !this.#workerIsAlive && this.#workerOnlineLock && action !== WORKER_ACTION__PING) { wait_for_worker_default(this, this.#workerOnlineLock, workerFile); this.#workerIsAlive = true; this.#workerOnlineLock = void 0; } const requestMessage = [action, payload]; const transferList = []; const worker = this.#worker; let channel = this.#channel; if (this.#createChannel()) { channel = this.#channel; const { workerPort: responsePort, semaphore: responseSemaphore } = channel; requestMessage.push({ responsePort, responseSemaphore }); transferList.push(responsePort); } try { worker.postMessage(requestMessage, transferList); } catch (postMessageError) { if (isDataCloneError(postMessageError)) { throw Object.assign( new DOMException( `Cannot serialize request data: ${util2.inspect(payload)}`, "DataCloneError" ), { requestData: payload, cause: postMessageError } ); } throw postMessageError; } const { stdio, exitCode, terminated, rejected, error, result } = channel.getResponse(timeout); if (stdio) { for (const { stream, chunk } of stdio) { import_node_process3.default[stream].write(chunk); } } if (terminated || exitCode) { worker.terminate(); channel.destroy(); this.#killWorker(worker); } if (rejected) { throw error; } return result; } }; var threads_worker_default = ThreadsWorker; // source/normalize-module.js var path = __toESM(require("node:path"), 1); var url = __toESM(require("node:url"), 1); var import_node_util2 = __toESM(require("node:util"), 1); var isString = (value) => typeof value === "string"; var filenameToModuleId = (filename) => url.pathToFileURL(filename).href; function toModuleSource(module3) { const href = module3?.href; if (isString(href)) { return href; } const url2 = module3?.url; if (isString(url2)) { return url2; } const filename = module3?.filename; if (isString(filename)) { return filenameToModuleId(filename); } if (!isString(module3) || module3.startsWith(".")) { throw new TypeError( `'module' should be an 'URL', 'import.meta' or an absolute path, got '${import_node_util2.default.inspect(module3)}'.` ); } if (path.isAbsolute(module3)) { return filenameToModuleId(module3); } return module3; } function normalizeModule(module3) { return { source: toModuleSource(module3) }; } var normalize_module_default = normalizeModule; // source/synchronizer.js var cacheResult = (cache, cacheKey, getResult) => { if (!cache.has(cacheKey)) { cache.set(cacheKey, getResult()); } return cache.get(cacheKey); }; var cachePathResult = (cache, path2, getResult) => cacheResult(cache, hashPath(path2), getResult); var Synchronizer = class _Synchronizer { static #instances = /* @__PURE__ */ new Map(); static create(module3, { isNormalizedModule = false } = {}) { module3 = isNormalizedModule ? module3 : normalize_module_default(module3); return cacheResult( this.#instances, JSON.stringify(module3), () => new _Synchronizer(module3) ); } #worker; #synchronizedFunctionStore = /* @__PURE__ */ new Map(); #informationStore = /* @__PURE__ */ new Map(); #ownKeysStore = /* @__PURE__ */ new Map(); #plainObjectStore = /* @__PURE__ */ new Map(); constructor(module3) { this.#worker = new threads_worker_default(module3); } getInformation(path2) { return cachePathResult( this.#informationStore, path2, () => this.#worker.sendAction(WORKER_ACTION__GET_INFORMATION, { path: path2 }) ); } setKnownInformation(path2, information) { this.#informationStore.set(hashPath(path2), information); } get(path2) { const information = this.getInformation(path2); switch (information.type) { case VALUE_TYPE__FUNCTION: return this.#createSynchronizedFunction(path2); case VALUE_TYPE__PRIMITIVE: return information.value; case VALUE_TYPE__PLAIN_OBJECT: return this.#createPlainObjectProxy(path2, information); default: return this.#worker.sendAction(WORKER_ACTION__GET, { path: path2 }); } } ownKeys(path2) { return cachePathResult( this.#ownKeysStore, path2, () => this.#worker.sendAction(WORKER_ACTION__OWN_KEYS, { path: path2 }) ); } apply(path2, argumentsList) { return this.#worker.sendAction(WORKER_ACTION__APPLY, { path: path2, argumentsList }); } #createSynchronizedFunction(path2) { return cachePathResult( this.#synchronizedFunctionStore, path2, () => (...argumentsList) => this.apply(path2, argumentsList) ); } createDefaultExportFunctionProxy() { const defaultExportFunction = this.get("default"); return new Proxy(defaultExportFunction, { get: (target, property) => this.get(property) }); } #createPlainObjectProxy(path2, { isNullPrototypeObject, properties }) { path2 = normalizePath(path2); return cachePathResult(this.#plainObjectStore, path2, () => { const object = isNullPrototypeObject ? /* @__PURE__ */ Object.create(null) : {}; for (const [property, propertyInformation] of properties) { if (propertyInformation?.type === VALUE_TYPE__PRIMITIVE) { object[property] = propertyInformation.value; } else { Object.defineProperty(object, property, { get: () => this.get([...path2, property]), enumerable: true, configurable: true }); } } return new Proxy(object, { get: (target, property, receiver) => { if (typeof property === "symbol" || properties.has(property)) { return Reflect.get(target, property, receiver); } return this.get([...path2, property]); } }); }); } createModule() { const module3 = Object.create(null, { [Symbol.toStringTag]: { value: "Module", enumerable: false } }); const specifiers = this.ownKeys(); return Object.defineProperties( module3, Object.fromEntries( specifiers.map((specifier) => [ specifier, { get: () => this.get(specifier), enumerable: true } ]) ) ); } }; var synchronizer_default = Synchronizer; // source/for-exports.js function makeSynchronizedFunctions(module3, implementation) { if (IS_SERVER) { return implementation; } const synchronizer = synchronizer_default.create(module3); synchronizer.setKnownInformation( void 0, get_value_information_default(implementation) ); return new Proxy(implementation, { get: (target, property) => typeof implementation[property] === "function" ? synchronizer.get(property) : target[property] }); } function makeSynchronizedFunction(module3, implementation, specifier = "default") { if (IS_SERVER) { return implementation; } const synchronizer = synchronizer_default.create(module3); synchronizer.setKnownInformation( specifier, get_value_information_default(implementation) ); return synchronizer.get(specifier); } // source/for-inline-functions.js function makeInlineFunctionSynchronized(implementation) { const code = typeof implementation === "function" ? implementation.toString() : implementation; const synchronizer = synchronizer_default.create( { type: MODULE_TYPE__INLINE_FUNCTION, code }, { isNormalizedModule: true } ); synchronizer.setKnownInformation(void 0, VALUE_INFORMATION__FUNCTION); return synchronizer.get("default"); } // source/for-modules.js function makeDefaultExportSynchronized(module3) { return synchronizer_default.create(module3).get("default"); } function makeModuleSynchronized(module3) { return synchronizer_default.create(module3).createModule(); } // source/client.js function makeSynchronized(module3, implementation) { if (typeof module3 === "function") { return makeInlineFunctionSynchronized(module3); } if (typeof implementation === "function") { return makeSynchronizedFunction(module3, implementation); } if (implementation) { return makeSynchronizedFunctions(module3, implementation); } const synchronizer = synchronizer_default.create(module3); const defaultExportType = synchronizer.getInformation("default").type; if (defaultExportType === VALUE_TYPE__FUNCTION) { return synchronizer.createDefaultExportFunctionProxy(); } return synchronizer.createModule(); } // source/index.js import_node_module.default.enableCompileCache?.(); if (IS_SERVER) { try { server_default(); } catch { } } else { setWorkFile(__filename); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { makeDefaultExportSynchronized, makeInlineFunctionSynchronized, makeModuleSynchronized, makeSynchronized, makeSynchronizedFunction, makeSynchronizedFunctions });