@comake/skl-js-engine 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"jsExecutor.d.ts","sourceRoot":"","sources":["../../src/JsExecutor/jsExecutor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAElE,oBAAY,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,UAAU;IASnB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAT5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,SAAS,CAAC,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;IACtE,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAGtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IAGrE;;;OAGG;WACW,cAAc,IAAI,OAAO;IAIvC;;;OAGG;WACW,oBAAoB,IAAI,WAAW;IAIpC,UAAU,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBxF,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAgBlD,YAAY;YAgBZ,oBAAoB;IAMlC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAyBhB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,gBAAgB,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAwB/G;;;OAGG;IACU,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;CAMpE"}
1
+ {"version":3,"file":"jsExecutor.d.ts","sourceRoot":"","sources":["../../src/JsExecutor/jsExecutor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAElE,oBAAY,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,UAAU;IASnB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAT5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,SAAS,CAAC,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAyB;IACtE,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAGtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IAGrE;;;OAGG;WACW,cAAc,IAAI,OAAO;IAIvC;;;OAGG;WACW,oBAAoB,IAAI,WAAW;IAIpC,UAAU,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBxF,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAgBlD,YAAY;YAgBZ,oBAAoB;IAMlC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAqChB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,gBAAgB,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAwB/G;;;OAGG;IACU,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;CAMpE"}
@@ -3,7 +3,6 @@
3
3
  /* eslint-disable callback-return */
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.JSExecutor = void 0;
6
- /* eslint-disable func-style */
7
6
  const errors_1 = require("./errors");
8
7
  const transport_1 = require("./transport");
9
8
  /**
@@ -115,17 +114,28 @@ class JSExecutor {
115
114
  return Promise.reject(new Error('JSExecutor is shutting down'));
116
115
  }
117
116
  const callbackPromise = Promise.resolve(this.rpcComplaintCallback(callback, args));
117
+ const instanceAbortSignal = this.abortController.signal;
118
+ const globalAbortSignal = JSExecutor.globalAbortController.signal;
119
+ let onAbort;
118
120
  // Create an abort promise
119
121
  const abortPromise = new Promise((_, reject) => {
120
- const onAbort = () => {
122
+ onAbort = () => {
121
123
  reject(new Error('JSExecutor is shutting down'));
122
124
  };
123
125
  // Listen to both instance and global abort signals
124
- this.abortController.signal.addEventListener('abort', onAbort, { once: true });
125
- JSExecutor.globalAbortController.signal.addEventListener('abort', onAbort, { once: true });
126
+ // NOTE: `{ once: true }` removes listeners only if abort fires. In normal request flow,
127
+ // abort rarely happens, so these listeners would otherwise accumulate per-call.
128
+ instanceAbortSignal.addEventListener('abort', onAbort, { once: true });
129
+ globalAbortSignal.addEventListener('abort', onAbort, { once: true });
126
130
  });
127
131
  // Race the callback against the abort signal
128
- return Promise.race([callbackPromise, abortPromise]);
132
+ return Promise.race([callbackPromise, abortPromise]).finally(() => {
133
+ // If the callback completes, remove abort listeners to prevent a listener leak.
134
+ if (onAbort) {
135
+ instanceAbortSignal.removeEventListener('abort', onAbort);
136
+ globalAbortSignal.removeEventListener('abort', onAbort);
137
+ }
138
+ });
129
139
  });
130
140
  }
131
141
  async execute(code, args, executionOptions) {
@@ -1 +1 @@
1
- {"version":3,"file":"jsExecutor.js","sourceRoot":"","sources":["../../src/JsExecutor/jsExecutor.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention */\n/* eslint-disable callback-return */\n\n/* eslint-disable func-style */\n\nimport { NotInitializedError } from './errors';\nimport type { TransportConfig } from './transport';\nimport { JsonRpcServer, ParentStdioTransport, STANDARD_METHODS } from './transport';\nimport type { ExecutionOptions, IShutdownOptions } from './types';\n\nexport type IJSCallbacks = Record<string, (...args: any[]) => any>;\n\n/**\n * JSExecutor V2 with enhanced abort control for immediate shutdown\n *\n * @example Writing abort-aware callbacks:\n * ```typescript\n * const jsExecutor = new JSExecutorV2(scriptPath, {\n * async myCallback(): Promise<string> {\n * return new Promise((resolve, reject) => {\n * const timeoutId = setTimeout(() => resolve('done'), 5000);\n *\n * // Listen to the global abort signal to clean up resources\n * const abortSignal = JSExecutorV2.getGlobalAbortSignal();\n * const onAbort = () => {\n * clearTimeout(timeoutId);\n * reject(new Error('Operation aborted'));\n * };\n *\n * if (abortSignal.aborted) {\n * onAbort();\n * return;\n * }\n *\n * abortSignal.addEventListener('abort', onAbort);\n * });\n * }\n * });\n * ```\n */\nexport class JSExecutor {\n private isInitialized = false;\n private server?: JsonRpcServer;\n private transport?: ParentStdioTransport;\n private readonly abortController = new AbortController();\n private static readonly globalAbortController = new AbortController();\n private shutdownTimeout?: NodeJS.Timeout;\n\n public constructor(\n private readonly executorScriptPath: string,\n private readonly callbacks: Record<string, (...args: any[]) => any>\n ) {}\n\n /**\n * Static method to check if any JSExecutor instance is being shut down\n * Callbacks can use this to check if they should abort their operations\n */\n public static isShuttingDown(): boolean {\n return JSExecutor.globalAbortController.signal.aborted;\n }\n\n /**\n * Get the global abort signal that callbacks can listen to\n * This allows callbacks to immediately abort when any JSExecutor shuts down\n */\n public static getGlobalAbortSignal(): AbortSignal {\n return JSExecutor.globalAbortController.signal;\n }\n\n public async initialize(config?: TransportConfig, executionOptions?: ExecutionOptions): Promise<void> {\n if (this.isInitialized) {\n return;\n }\n\n this.server = new JsonRpcServer({\n // 1 hour\n requestTimeout: 60 * 60 * 1000\n });\n\n // Register callbacks with the server\n Object.entries(this.callbacks).forEach(([ methodName, callback ]) => {\n this.server!.registerMethod(methodName, this.wrapCallbackWithAbort(callback));\n });\n\n // Create and initialize the transport once\n this.transport = new ParentStdioTransport(this.executorScriptPath, this.server);\n\n await this.transport.initialize(config, executionOptions);\n\n this.isInitialized = true;\n }\n\n public async shutdown(options?: IShutdownOptions): Promise<void> {\n // Clear any existing shutdown timeout\n if (this.shutdownTimeout) {\n clearTimeout(this.shutdownTimeout);\n this.shutdownTimeout = undefined;\n }\n\n if (options?.afterTimeout) {\n this.shutdownTimeout = setTimeout(async() => {\n await this.shutdownCore();\n }, options.afterTimeout);\n } else {\n await this.shutdownCore();\n }\n }\n\n private async shutdownCore(): Promise<void> {\n // // Signal global abort first\n // JSExecutor.globalAbortController.abort();\n\n // Then signal this instance's abort\n this.abortController.abort();\n\n if (this.transport) {\n await this.transport.close();\n this.transport = undefined;\n }\n\n this.isInitialized = false;\n this.server = undefined;\n }\n\n private async rpcComplaintCallback(callback: (...args: any[]) => any, args: any[]): Promise<any> {\n const result = await callback(...args);\n // RPC expects a result object\n return result ?? {};\n }\n\n /**\n * Wrap a callback with abort functionality\n * The callback will be automatically aborted if JSExecutor is shut down\n */\n private wrapCallbackWithAbort<T extends (...args: any[]) => any>(callback: T): T {\n return ((...args: any[]) => {\n // Check if we're shutting down before executing\n if (this.abortController.signal.aborted || JSExecutor.globalAbortController.signal.aborted) {\n return Promise.reject(new Error('JSExecutor is shutting down'));\n }\n\n const callbackPromise = Promise.resolve(this.rpcComplaintCallback(callback, args));\n\n // Create an abort promise\n const abortPromise = new Promise((_, reject) => {\n const onAbort = (): void => {\n reject(new Error('JSExecutor is shutting down'));\n };\n\n // Listen to both instance and global abort signals\n this.abortController.signal.addEventListener('abort', onAbort, { once: true });\n JSExecutor.globalAbortController.signal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Race the callback against the abort signal\n return Promise.race([ callbackPromise, abortPromise ]);\n }) as T;\n }\n\n public async execute(code: string, args: Record<string, any>, executionOptions: ExecutionOptions): Promise<any> {\n if (!this.isInitialized) {\n throw new NotInitializedError('JSExecutor is not initialized, please call initialize() first');\n }\n\n if (!this.transport || !this.transport.isReady()) {\n throw new Error('Transport is not ready');\n }\n\n const response = await this.transport.request(\n STANDARD_METHODS.executeCode,\n {\n code,\n args,\n functionName: executionOptions.functionName\n },\n {\n timeout: executionOptions.timeout,\n retries: executionOptions.retries ?? 0\n }\n );\n return response;\n }\n\n /**\n * Legacy method for backward compatibility\n * @deprecated Use execute() instead\n */\n public async call(methodName: string, ...args: any[]): Promise<any> {\n if (!this.transport || !this.transport.isReady()) {\n throw new Error('Transport is not ready');\n }\n return this.transport.request(methodName, args);\n }\n}\n"]}
1
+ {"version":3,"file":"jsExecutor.js","sourceRoot":"","sources":["../../src/JsExecutor/jsExecutor.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/naming-convention */\n/* eslint-disable callback-return */\n\n \n\nimport { NotInitializedError } from './errors';\nimport type { TransportConfig } from './transport';\nimport { JsonRpcServer, ParentStdioTransport, STANDARD_METHODS } from './transport';\nimport type { ExecutionOptions, IShutdownOptions } from './types';\n\nexport type IJSCallbacks = Record<string, (...args: any[]) => any>;\n\n/**\n * JSExecutor V2 with enhanced abort control for immediate shutdown\n *\n * @example Writing abort-aware callbacks:\n * ```typescript\n * const jsExecutor = new JSExecutorV2(scriptPath, {\n * async myCallback(): Promise<string> {\n * return new Promise((resolve, reject) => {\n * const timeoutId = setTimeout(() => resolve('done'), 5000);\n *\n * // Listen to the global abort signal to clean up resources\n * const abortSignal = JSExecutorV2.getGlobalAbortSignal();\n * const onAbort = () => {\n * clearTimeout(timeoutId);\n * reject(new Error('Operation aborted'));\n * };\n *\n * if (abortSignal.aborted) {\n * onAbort();\n * return;\n * }\n *\n * abortSignal.addEventListener('abort', onAbort);\n * });\n * }\n * });\n * ```\n */\nexport class JSExecutor {\n private isInitialized = false;\n private server?: JsonRpcServer;\n private transport?: ParentStdioTransport;\n private readonly abortController = new AbortController();\n private static readonly globalAbortController = new AbortController();\n private shutdownTimeout?: NodeJS.Timeout;\n\n public constructor(\n private readonly executorScriptPath: string,\n private readonly callbacks: Record<string, (...args: any[]) => any>\n ) {}\n\n /**\n * Static method to check if any JSExecutor instance is being shut down\n * Callbacks can use this to check if they should abort their operations\n */\n public static isShuttingDown(): boolean {\n return JSExecutor.globalAbortController.signal.aborted;\n }\n\n /**\n * Get the global abort signal that callbacks can listen to\n * This allows callbacks to immediately abort when any JSExecutor shuts down\n */\n public static getGlobalAbortSignal(): AbortSignal {\n return JSExecutor.globalAbortController.signal;\n }\n\n public async initialize(config?: TransportConfig, executionOptions?: ExecutionOptions): Promise<void> {\n if (this.isInitialized) {\n return;\n }\n\n this.server = new JsonRpcServer({\n // 1 hour\n requestTimeout: 60 * 60 * 1000\n });\n\n // Register callbacks with the server\n Object.entries(this.callbacks).forEach(([ methodName, callback ]) => {\n this.server!.registerMethod(methodName, this.wrapCallbackWithAbort(callback));\n });\n\n // Create and initialize the transport once\n this.transport = new ParentStdioTransport(this.executorScriptPath, this.server);\n\n await this.transport.initialize(config, executionOptions);\n\n this.isInitialized = true;\n }\n\n public async shutdown(options?: IShutdownOptions): Promise<void> {\n // Clear any existing shutdown timeout\n if (this.shutdownTimeout) {\n clearTimeout(this.shutdownTimeout);\n this.shutdownTimeout = undefined;\n }\n\n if (options?.afterTimeout) {\n this.shutdownTimeout = setTimeout(async() => {\n await this.shutdownCore();\n }, options.afterTimeout);\n } else {\n await this.shutdownCore();\n }\n }\n\n private async shutdownCore(): Promise<void> {\n // // Signal global abort first\n // JSExecutor.globalAbortController.abort();\n\n // Then signal this instance's abort\n this.abortController.abort();\n\n if (this.transport) {\n await this.transport.close();\n this.transport = undefined;\n }\n\n this.isInitialized = false;\n this.server = undefined;\n }\n\n private async rpcComplaintCallback(callback: (...args: any[]) => any, args: any[]): Promise<any> {\n const result = await callback(...args);\n // RPC expects a result object\n return result ?? {};\n }\n\n /**\n * Wrap a callback with abort functionality\n * The callback will be automatically aborted if JSExecutor is shut down\n */\n private wrapCallbackWithAbort<T extends (...args: any[]) => any>(callback: T): T {\n return ((...args: any[]) => {\n // Check if we're shutting down before executing\n if (this.abortController.signal.aborted || JSExecutor.globalAbortController.signal.aborted) {\n return Promise.reject(new Error('JSExecutor is shutting down'));\n }\n\n const callbackPromise = Promise.resolve(this.rpcComplaintCallback(callback, args));\n\n const instanceAbortSignal = this.abortController.signal;\n const globalAbortSignal = JSExecutor.globalAbortController.signal;\n let onAbort: (() => void) | undefined;\n\n // Create an abort promise\n const abortPromise = new Promise((_, reject) => {\n onAbort = (): void => {\n reject(new Error('JSExecutor is shutting down'));\n };\n\n // Listen to both instance and global abort signals\n // NOTE: `{ once: true }` removes listeners only if abort fires. In normal request flow,\n // abort rarely happens, so these listeners would otherwise accumulate per-call.\n instanceAbortSignal.addEventListener('abort', onAbort, { once: true });\n globalAbortSignal.addEventListener('abort', onAbort, { once: true });\n });\n\n // Race the callback against the abort signal\n return Promise.race([ callbackPromise, abortPromise ]).finally(() => {\n // If the callback completes, remove abort listeners to prevent a listener leak.\n if (onAbort) {\n instanceAbortSignal.removeEventListener('abort', onAbort);\n globalAbortSignal.removeEventListener('abort', onAbort);\n }\n });\n }) as T;\n }\n\n public async execute(code: string, args: Record<string, any>, executionOptions: ExecutionOptions): Promise<any> {\n if (!this.isInitialized) {\n throw new NotInitializedError('JSExecutor is not initialized, please call initialize() first');\n }\n\n if (!this.transport || !this.transport.isReady()) {\n throw new Error('Transport is not ready');\n }\n\n const response = await this.transport.request(\n STANDARD_METHODS.executeCode,\n {\n code,\n args,\n functionName: executionOptions.functionName\n },\n {\n timeout: executionOptions.timeout,\n retries: executionOptions.retries ?? 0\n }\n );\n return response;\n }\n\n /**\n * Legacy method for backward compatibility\n * @deprecated Use execute() instead\n */\n public async call(methodName: string, ...args: any[]): Promise<any> {\n if (!this.transport || !this.transport.isReady()) {\n throw new Error('Transport is not ready');\n }\n return this.transport.request(methodName, args);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comake/skl-js-engine",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Standard Knowledge Language Javascript Engine",
5
5
  "keywords": [
6
6
  "skl",
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
2
  /* eslint-disable callback-return */
3
3
 
4
- /* eslint-disable func-style */
4
+
5
5
 
6
6
  import { NotInitializedError } from './errors';
7
7
  import type { TransportConfig } from './transport';
@@ -141,19 +141,31 @@ export class JSExecutor {
141
141
 
142
142
  const callbackPromise = Promise.resolve(this.rpcComplaintCallback(callback, args));
143
143
 
144
+ const instanceAbortSignal = this.abortController.signal;
145
+ const globalAbortSignal = JSExecutor.globalAbortController.signal;
146
+ let onAbort: (() => void) | undefined;
147
+
144
148
  // Create an abort promise
145
149
  const abortPromise = new Promise((_, reject) => {
146
- const onAbort = (): void => {
150
+ onAbort = (): void => {
147
151
  reject(new Error('JSExecutor is shutting down'));
148
152
  };
149
153
 
150
154
  // Listen to both instance and global abort signals
151
- this.abortController.signal.addEventListener('abort', onAbort, { once: true });
152
- JSExecutor.globalAbortController.signal.addEventListener('abort', onAbort, { once: true });
155
+ // NOTE: `{ once: true }` removes listeners only if abort fires. In normal request flow,
156
+ // abort rarely happens, so these listeners would otherwise accumulate per-call.
157
+ instanceAbortSignal.addEventListener('abort', onAbort, { once: true });
158
+ globalAbortSignal.addEventListener('abort', onAbort, { once: true });
153
159
  });
154
160
 
155
161
  // Race the callback against the abort signal
156
- return Promise.race([ callbackPromise, abortPromise ]);
162
+ return Promise.race([ callbackPromise, abortPromise ]).finally(() => {
163
+ // If the callback completes, remove abort listeners to prevent a listener leak.
164
+ if (onAbort) {
165
+ instanceAbortSignal.removeEventListener('abort', onAbort);
166
+ globalAbortSignal.removeEventListener('abort', onAbort);
167
+ }
168
+ });
157
169
  }) as T;
158
170
  }
159
171