hayai-db
Version:
⚡ Instantly create and manage local databases with one command
176 lines (175 loc) • 6.13 kB
JavaScript
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);
};