UNPKG

@comake/skl-js-engine

Version:

Standard Knowledge Language Javascript Engine

215 lines 7.45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChildStdioTransport = void 0; /* eslint-disable indent */ const node_events_1 = require("node:events"); const JsonRpcClient_1 = require("../../jsonRpc/JsonRpcClient"); const JsonRpcServer_1 = require("../../jsonRpc/JsonRpcServer"); const MessageUtils_1 = require("../utils/MessageUtils"); /** * Custom logger that outputs to stderr to avoid contaminating stdout JSON-RPC channel */ class ChildProcessLogger { constructor() { this.metadata = {}; } setMetadata(metadata) { this.metadata = { ...this.metadata, ...metadata }; } log(message, data) { const logEntry = { message, metadata: this.metadata, ...data && { data } }; // Use stderr to avoid contaminating stdout JSON-RPC channel process.stderr.write(`${message} ${JSON.stringify(logEntry.metadata)} ${data ? JSON.stringify(data) : ''}\n`); } error(message, error) { const logEntry = { level: 'error', message, metadata: this.metadata, ...error && { error: error.message, stack: error.stack } }; process.stderr.write(`ERROR: ${JSON.stringify(logEntry)}\n`); } } /** * Bidirectional StdioTransport for child processes * This class handles both server (receiving requests) and client (sending requests) functionality * over a single stdio channel with proper message routing. * * Usage in child process: * ```typescript * const transport = new ChildStdioTransport(); * * // Register methods that parent can call * transport.registerMethod('ping', async () => 'pong'); * * // Make requests to parent * const result = await transport.request('getTime'); * ``` */ class ChildStdioTransport extends node_events_1.EventEmitter { constructor() { super(); this.logger = new ChildProcessLogger(); this.messageBuffer = new MessageUtils_1.MessageBuffer(); this.initialized = false; this.server = new JsonRpcServer_1.JsonRpcServer(); this.client = new JsonRpcClient_1.JsonRpcClient(); // Set up the client transport that routes through our stdio const clientTransport = { send: async (message) => { this.sendMessage(message); }, onMessage() { // The message routing will handle client responses automatically }, async close() { // Nothing to close for stdio } }; this.client.setTransport(clientTransport); this.setupEventHandlers(); } /** * Set a name for this transport (used in logging) */ setName(name) { this.name = name; this.logger.setMetadata({ name, transport: 'ChildStdioTransport' }); } /** * Initialize the bidirectional transport * This should be called once after setting up all methods */ async initialize() { if (this.initialized) { throw new Error('Transport already initialized'); } this.initialized = true; this.setupStdioCommunication(); this.logger.log('Transport initialized'); } /** * Register a method that the parent can call * @param method - Method name * @param handler - Method handler function */ registerMethod(method, handler) { this.server.registerMethod(method, handler); } /** * Send a request to the parent process * @param method - Method name * @param params - Method parameters * @param options - Request options * @returns Promise resolving to the response */ async request(method, params, options) { if (!this.initialized) { throw new Error('Transport not initialized. Call initialize() first.'); } return this.client.request(method, params, options); } /** * Send a notification to the parent process (no response expected) * @param method - Method name * @param params - Method parameters */ async notify(method, params) { if (!this.initialized) { throw new Error('Transport not initialized. Call initialize() first.'); } return this.client.notify(method, params); } /** * Get transport statistics */ getStats() { return { serverMethods: 0, pendingRequests: this.client.getStats().pendingRequests, initialized: this.initialized }; } /** * Close the transport and cleanup resources */ async close() { await this.client.close(); this.initialized = false; this.removeAllListeners(); } /** * Set up stdio communication handlers */ setupStdioCommunication() { // Handle incoming data from parent process.stdin.on('data', async (data) => { const messages = this.messageBuffer.addData(data.toString()); for (const messageData of messages) { await this.handleIncomingMessage(messageData); } }); // Handle process termination gracefully process.on('SIGTERM', () => { this.close().finally(() => process.exit(0)); }); process.on('SIGINT', () => { this.close().finally(() => process.exit(0)); }); } /** * Handle incoming message and route to appropriate handler */ async handleIncomingMessage(messageData) { try { const message = JSON.parse(messageData); // Check if this is a response to our request (has 'result' or 'error' and 'id') if ((0, MessageUtils_1.isResponse)(message)) { // This is a response to a request we made - route to client this.logger.log(`Routing response to client: ${message.id}`); await this.client.handleIncomingMessage(messageData); } else { // This is a request or notification for us - route to server this.logger.log(`Routing request to server: ${message.method}`); const response = await this.server.processMessage(messageData); this.logger.log('Sending response to parent process', response); // Only send response if there is one (requests get responses, notifications don't) if (response) { this.sendMessage(JSON.stringify(response)); } } } catch (error) { process.stderr.write(`Error processing message: ${error.message}\n`); this.emit('error', error); } } /** * Send a message to the parent process */ sendMessage(message) { this.logger.log(`Sending: ${message}`); process.stdout.write(`${message}\n`); } /** * Set up event handlers */ setupEventHandlers() { this.server.on('error', (error) => { this.logger.error('Server error', error); this.emit('error', error); }); this.client.on('error', (error) => { this.logger.error('Client error', error); this.emit('error', error); }); } } exports.ChildStdioTransport = ChildStdioTransport; //# sourceMappingURL=ChildStdioTransport.js.map