UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

188 lines (177 loc) 5.61 kB
/** * Socket server for the first TypeScript language service worker * * The first worker to start creates a socket server that other workers can connect to. * This allows sharing a single TypeScript language service across all Next.js worker processes. * * On Unix systems, this uses Unix domain sockets. * On Windows, this uses named pipes (which Node.js net module supports transparently). */ import { createServer } from 'node:net'; import { unlink, stat } from 'node:fs/promises'; import { getSocketPath, ensureSocketDir } from "./socketClient.mjs"; const isWindows = process.platform === 'win32'; /** * Check if a file exists */ async function fileExists(path) { try { await stat(path); return true; } catch { return false; } } /** * Socket server that handles requests from other workers */ export class SocketServer { connections = new Set(); /** * Request queue to serialize processTypes calls. * The TypeScript language service is a singleton that uses setRootFiles() * on each call — concurrent requests would thrash the program cache. */ requestQueue = Promise.resolve(); constructor(requestHandler, socketPath, server) { this.socketPath = socketPath; this.requestHandler = requestHandler; this.server = server; } /** * Create and initialize a socket server */ static async create(requestHandler, socketDir) { const socketPath = getSocketPath(socketDir); // Ensure the directory exists (only needed for Unix sockets) if (!isWindows) { // Unix domain sockets have a max path length (sun_path field in sockaddr_un): // Linux: 108 bytes, macOS/BSD: 104 bytes const maxSocketPath = process.platform === 'darwin' ? 104 : 108; if (Buffer.byteLength(socketPath) >= maxSocketPath) { throw new Error(`Socket path exceeds the maximum length of ${maxSocketPath} bytes ` + `for this platform (${Buffer.byteLength(socketPath)} bytes): ${socketPath}. ` + `Use a shorter socketDir path or avoid deeply nesting your project directory.`); } await ensureSocketDir(socketDir); // Clean up existing socket file if it exists (might be stale from previous build) if (await fileExists(socketPath)) { try { await unlink(socketPath); } catch { // Ignore cleanup errors } } } const server = createServer(); const instance = new SocketServer(requestHandler, socketPath, server); server.on('connection', socket => { instance.handleConnection(socket); }); server.on('error', error => { console.error('[SocketServer] Server error:', error); }); return instance; } /** * Start the socket server */ start() { return new Promise((resolve, reject) => { this.server.listen(this.socketPath, () => { resolve(); }); this.server.on('error', reject); }); } /** * Handle incoming client connection */ handleConnection(socket) { this.connections.add(socket); let buffer = ''; socket.on('data', data => { buffer += data.toString(); // Process complete messages (delimited by newlines) const messages = buffer.split('\n'); buffer = messages.pop() || ''; for (const messageStr of messages) { if (!messageStr.trim()) { continue; } try { const message = JSON.parse(messageStr); // Enqueue message for serialized processing this.handleMessage(socket, message); } catch (error) { console.error('[SocketServer] Failed to parse message:', error); this.sendResponse(socket, { id: 'unknown', type: 'error', data: { error: 'Invalid message format' } }); } } }); socket.on('end', () => { this.connections.delete(socket); }); socket.on('error', error => { console.error('[SocketServer] Socket error:', error); this.connections.delete(socket); }); } /** * Handle incoming message from client. * Requests are serialized through a queue to prevent concurrent processTypes * calls from thrashing the singleton TypeScript language service. */ handleMessage(socket, message) { if (message.type === 'process-types' && message.data) { this.requestQueue = this.requestQueue.then(async () => { try { const result = await this.requestHandler(message.data); this.sendResponse(socket, { id: message.id, type: 'success', data: result }); } catch (error) { console.error('[SocketServer] Error handling message:', error); this.sendResponse(socket, { id: message.id, type: 'error', data: { error: error instanceof Error ? error.message : String(error) } }); } }).catch(error => { console.error('[SocketServer] Unexpected queue error:', error); }); } } /** * Send response to client */ sendResponse(socket, response) { socket.write(`${JSON.stringify(response)}\n`); } /** * Shutdown the server */ shutdown() { // Close all connections this.connections.forEach(socket => { socket.end(); }); this.server.close(() => { // Clean up socket file asynchronously (only for Unix sockets) if (!isWindows) { unlink(this.socketPath).catch(() => { // Ignore cleanup errors }); } }); } }