UNPKG

hayai-db

Version:

⚡ Instantly create and manage local databases with one command

176 lines (175 loc) 6.13 kB
import * as net from 'net'; import { readFile, writeFile, access } from 'fs/promises'; import { constants } from 'fs'; import * as path from 'path'; import { getConfig, getDataDirectory } from './config.js'; export class PortManager { static instance; allocatedPorts = new Map(); portRange = { start: 5000, end: 6000 }; constructor() { } static getInstance() { if (!PortManager.instance) { PortManager.instance = new PortManager(); } return PortManager.instance; } async initialize() { const config = await getConfig(); this.portRange = config.defaults.port_range; await this.loadAllocations(); } async allocatePort(serviceName, preferredPort) { await this.initialize(); // If a preferred port is specified and available, use it if (preferredPort && await this.isPortAvailable(preferredPort)) { if (!this.allocatedPorts.has(preferredPort)) { this.allocatedPorts.set(preferredPort, serviceName); await this.saveAllocations(); return preferredPort; } } // Find the next available port in the range for (let port = this.portRange.start; port <= this.portRange.end; port++) { if (!this.allocatedPorts.has(port) && await this.isPortAvailable(port)) { this.allocatedPorts.set(port, serviceName); await this.saveAllocations(); return port; } } throw new Error(`No available ports in range ${this.portRange.start}-${this.portRange.end}`); } async deallocatePort(port) { this.allocatedPorts.delete(port); await this.saveAllocations(); } getPortAllocations() { const allocations = []; for (const [port, service] of this.allocatedPorts.entries()) { allocations.push({ port, service, status: 'allocated', }); } return allocations; } isPortAllocated(port) { return this.allocatedPorts.has(port); } getServiceByPort(port) { return this.allocatedPorts.get(port); } getPortByService(serviceName) { for (const [port, service] of this.allocatedPorts.entries()) { if (service === serviceName) { return port; } } return undefined; } async isPortAvailable(port) { return new Promise((resolve) => { const server = net.createServer(); server.listen(port, () => { server.once('close', () => { resolve(true); }); server.close(); }); server.on('error', () => { resolve(false); }); }); } async findAvailablePortsInRange(count) { await this.initialize(); const availablePorts = []; for (let port = this.portRange.start; port <= this.portRange.end && availablePorts.length < count; port++) { if (!this.allocatedPorts.has(port) && await this.isPortAvailable(port)) { availablePorts.push(port); } } return availablePorts; } async resetAllocations() { this.allocatedPorts.clear(); } getPortRangeInfo() { const total = this.portRange.end - this.portRange.start + 1; const allocated = this.allocatedPorts.size; const available = total - allocated; return { start: this.portRange.start, end: this.portRange.end, total, allocated, available, }; } async pathExists(filePath) { try { await access(filePath, constants.F_OK); return true; } catch { return false; } } async loadAllocations() { const dataDir = await getDataDirectory(); const allocationsFile = path.join(dataDir, 'port-allocations.json'); if (await this.pathExists(allocationsFile)) { try { const content = await readFile(allocationsFile, 'utf-8'); const allocationsData = JSON.parse(content); this.allocatedPorts.clear(); for (const [port, service] of Object.entries(allocationsData)) { this.allocatedPorts.set(parseInt(port), service); } } catch (error) { console.warn('Failed to load port allocations:', error); } } } async saveAllocations() { const dataDir = await getDataDirectory(); const allocationsFile = path.join(dataDir, 'port-allocations.json'); const allocationsData = {}; for (const [port, service] of this.allocatedPorts) { allocationsData[port.toString()] = service; } try { await writeFile(allocationsFile, JSON.stringify(allocationsData, null, 2), 'utf-8'); } catch (error) { console.warn('Failed to save port allocations:', error); } } } // Convenience functions for global access export const allocatePort = async (serviceName, preferredPort) => { const manager = PortManager.getInstance(); return await manager.allocatePort(serviceName, preferredPort); }; export const deallocatePort = async (port) => { const manager = PortManager.getInstance(); await manager.deallocatePort(port); }; export const getPortAllocations = () => { const manager = PortManager.getInstance(); return manager.getPortAllocations(); }; export const isPortAllocated = (port) => { const manager = PortManager.getInstance(); return manager.isPortAllocated(port); }; export const getServiceByPort = (port) => { const manager = PortManager.getInstance(); return manager.getServiceByPort(port); }; export const getPortByService = (serviceName) => { const manager = PortManager.getInstance(); return manager.getPortByService(serviceName); };