UNPKG

@comake/skl-js-engine

Version:

Standard Knowledge Language Javascript Engine

161 lines 6.05 kB
"use strict"; /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable callback-return */ Object.defineProperty(exports, "__esModule", { value: true }); exports.JSExecutor = void 0; /* eslint-disable func-style */ const errors_1 = require("./errors"); const transport_1 = require("./transport"); /** * JSExecutor V2 with enhanced abort control for immediate shutdown * * @example Writing abort-aware callbacks: * ```typescript * const jsExecutor = new JSExecutorV2(scriptPath, { * async myCallback(): Promise<string> { * return new Promise((resolve, reject) => { * const timeoutId = setTimeout(() => resolve('done'), 5000); * * // Listen to the global abort signal to clean up resources * const abortSignal = JSExecutorV2.getGlobalAbortSignal(); * const onAbort = () => { * clearTimeout(timeoutId); * reject(new Error('Operation aborted')); * }; * * if (abortSignal.aborted) { * onAbort(); * return; * } * * abortSignal.addEventListener('abort', onAbort); * }); * } * }); * ``` */ class JSExecutor { constructor(executorScriptPath, callbacks) { this.executorScriptPath = executorScriptPath; this.callbacks = callbacks; this.isInitialized = false; this.abortController = new AbortController(); } /** * Static method to check if any JSExecutor instance is being shut down * Callbacks can use this to check if they should abort their operations */ static isShuttingDown() { return JSExecutor.globalAbortController.signal.aborted; } /** * Get the global abort signal that callbacks can listen to * This allows callbacks to immediately abort when any JSExecutor shuts down */ static getGlobalAbortSignal() { return JSExecutor.globalAbortController.signal; } async initialize(config, executionOptions) { if (this.isInitialized) { return; } this.server = new transport_1.JsonRpcServer({ // 1 hour requestTimeout: 60 * 60 * 1000 }); // Register callbacks with the server Object.entries(this.callbacks).forEach(([methodName, callback]) => { this.server.registerMethod(methodName, this.wrapCallbackWithAbort(callback)); }); // Create and initialize the transport once this.transport = new transport_1.ParentStdioTransport(this.executorScriptPath, this.server); await this.transport.initialize(config, executionOptions); this.isInitialized = true; } async shutdown(options) { // Clear any existing shutdown timeout if (this.shutdownTimeout) { clearTimeout(this.shutdownTimeout); this.shutdownTimeout = undefined; } if (options?.afterTimeout) { this.shutdownTimeout = setTimeout(async () => { await this.shutdownCore(); }, options.afterTimeout); } else { await this.shutdownCore(); } } async shutdownCore() { // // Signal global abort first // JSExecutor.globalAbortController.abort(); // Then signal this instance's abort this.abortController.abort(); if (this.transport) { await this.transport.close(); this.transport = undefined; } this.isInitialized = false; this.server = undefined; } async rpcComplaintCallback(callback, args) { const result = await callback(...args); // RPC expects a result object return result ?? {}; } /** * Wrap a callback with abort functionality * The callback will be automatically aborted if JSExecutor is shut down */ wrapCallbackWithAbort(callback) { return ((...args) => { // Check if we're shutting down before executing if (this.abortController.signal.aborted || JSExecutor.globalAbortController.signal.aborted) { return Promise.reject(new Error('JSExecutor is shutting down')); } const callbackPromise = Promise.resolve(this.rpcComplaintCallback(callback, args)); // Create an abort promise const abortPromise = new Promise((_, reject) => { const onAbort = () => { reject(new Error('JSExecutor is shutting down')); }; // Listen to both instance and global abort signals this.abortController.signal.addEventListener('abort', onAbort, { once: true }); JSExecutor.globalAbortController.signal.addEventListener('abort', onAbort, { once: true }); }); // Race the callback against the abort signal return Promise.race([callbackPromise, abortPromise]); }); } async execute(code, args, executionOptions) { if (!this.isInitialized) { throw new errors_1.NotInitializedError('JSExecutor is not initialized, please call initialize() first'); } if (!this.transport || !this.transport.isReady()) { throw new Error('Transport is not ready'); } const response = await this.transport.request(transport_1.STANDARD_METHODS.executeCode, { code, args, functionName: executionOptions.functionName }, { timeout: executionOptions.timeout, retries: executionOptions.retries ?? 0 }); return response; } /** * Legacy method for backward compatibility * @deprecated Use execute() instead */ async call(methodName, ...args) { if (!this.transport || !this.transport.isReady()) { throw new Error('Transport is not ready'); } return this.transport.request(methodName, args); } } exports.JSExecutor = JSExecutor; JSExecutor.globalAbortController = new AbortController(); //# sourceMappingURL=jsExecutor.js.map