UNPKG

axiodb

Version:

The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platfor

338 lines 13.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AxioDBCloud = void 0; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ const net_1 = require("net"); const crypto_1 = require("crypto"); const events_1 = require("events"); const protocol_1 = require("../tcp/config/protocol"); const command_types_1 = require("../tcp/types/command.types"); const client_types_1 = require("./types/client.types"); const DatabaseProxy_1 = __importDefault(require("./DatabaseProxy")); /** * AxioDBCloud - TCP Client for remote AxioDB access */ class AxioDBCloud extends events_1.EventEmitter { constructor(connectionString, options) { super(); this.socket = null; this.messageBuffer = new protocol_1.MessageBuffer(); this.pendingRequests = new Map(); this.connectionState = client_types_1.ConnectionState.DISCONNECTED; this.reconnectAttempt = 0; this.heartbeatInterval = null; // Increase max listeners for reconnection scenarios this.setMaxListeners(20); // Parse connection string const parsed = this.parseConnectionString(connectionString); this.host = parsed.host; this.port = parsed.port; // Set options with defaults this.options = { timeout: (options === null || options === void 0 ? void 0 : options.timeout) || 30000, reconnectAttempts: (options === null || options === void 0 ? void 0 : options.reconnectAttempts) || 10, reconnectDelay: (options === null || options === void 0 ? void 0 : options.reconnectDelay) || 1000, heartbeatInterval: (options === null || options === void 0 ? void 0 : options.heartbeatInterval) || 30000, }; } /** * Parse connection string: axiodb://host:port */ parseConnectionString(connectionString) { const match = connectionString.match(/^axiodb:\/\/([^:]+):(\d+)$/); if (!match) { throw new Error('Invalid connection string. Format: axiodb://host:port'); } return { host: match[1], port: parseInt(match[2], 10), }; } /** * Connect to AxioDB TCP server */ connect() { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { if (this.connectionState === client_types_1.ConnectionState.CONNECTED) { return resolve(); } // Clean up old socket if it exists if (this.socket) { this.socket.removeAllListeners(); if (!this.socket.destroyed) { this.socket.destroy(); } this.socket = null; } this.connectionState = client_types_1.ConnectionState.CONNECTING; this.socket = new net_1.Socket(); // Setup socket event handlers this.setupSocketHandlers(); // Connection timeout const connectionTimeout = setTimeout(() => { var _a; (_a = this.socket) === null || _a === void 0 ? void 0 : _a.destroy(); this.connectionState = client_types_1.ConnectionState.FAILED; reject(new Error('Connection timeout')); }, this.options.timeout); // Connect to server this.socket.connect(this.port, this.host, () => { clearTimeout(connectionTimeout); this.connectionState = client_types_1.ConnectionState.CONNECTED; this.reconnectAttempt = 0; this.startHeartbeat(); this.emit('connected'); resolve(); }); // Handle connection errors this.socket.once('error', (error) => { clearTimeout(connectionTimeout); this.connectionState = client_types_1.ConnectionState.FAILED; reject(error); }); }); }); } /** * Setup socket event handlers */ setupSocketHandlers() { if (!this.socket) return; this.socket.on('data', (chunk) => { var _a; try { const messages = this.messageBuffer.addChunk(chunk); for (const message of messages) { this.handleResponse(message); } } catch (error) { // Clear buffer on protocol errors to prevent cascade failures this.messageBuffer.clear(); // Check if error is due to connecting to wrong port (HTTP instead of TCP) if (error instanceof Error && error.message.includes('Message exceeds maximum size')) { const enhancedError = new Error('Protocol error: Message exceeds maximum size. Are you connecting to the correct port? ' + 'AxioDBCloud uses TCP port (default: 27019), not HTTP port (27018).'); this.emit('error', enhancedError); (_a = this.socket) === null || _a === void 0 ? void 0 : _a.destroy(); } else { this.emit('error', error); } } }); this.socket.on('error', (error) => { this.emit('error', error); this.handleDisconnection(); }); this.socket.on('close', (hadError) => { this.emit('disconnected', hadError); this.handleDisconnection(); }); this.socket.on('end', () => { this.handleDisconnection(); }); } /** * Handle server response */ handleResponse(response) { const pending = this.pendingRequests.get(response.id); if (pending) { clearTimeout(pending.timeout); this.pendingRequests.delete(response.id); if (response.statusCode >= 200 && response.statusCode < 300) { pending.resolve(response.data); } else { pending.reject(new Error(response.error || response.message)); } } else { console.warn(`[AxioDBCloud] Received response for unknown request: ${response.id}`); } } /** * Handle disconnection */ handleDisconnection() { this.connectionState = client_types_1.ConnectionState.DISCONNECTED; this.stopHeartbeat(); // Clean up socket listeners if (this.socket) { this.socket.removeAllListeners(); } // Reject all pending requests for (const [id, pending] of this.pendingRequests.entries()) { clearTimeout(pending.timeout); pending.reject(new Error('Connection lost')); this.pendingRequests.delete(id); } // Attempt reconnection if (this.reconnectAttempt < this.options.reconnectAttempts) { this.attemptReconnect(); } else { this.emit('failed', new Error('Max reconnection attempts reached')); } } /** * Attempt to reconnect */ attemptReconnect() { return __awaiter(this, void 0, void 0, function* () { this.reconnectAttempt++; const delay = Math.min(this.options.reconnectDelay * Math.pow(2, this.reconnectAttempt - 1), 30000); this.emit('reconnecting', this.reconnectAttempt); this.connectionState = client_types_1.ConnectionState.RECONNECTING; setTimeout(() => __awaiter(this, void 0, void 0, function* () { try { yield this.connect(); this.emit('reconnected'); } catch (error) { // Reconnection will be attempted again in handleDisconnection } }), delay); }); } /** * Start heartbeat */ startHeartbeat() { this.heartbeatInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () { try { yield this.sendCommand(command_types_1.CommandType.PING, {}); } catch (error) { console.warn('[AxioDBCloud] Heartbeat failed:', error); } }), this.options.heartbeatInterval); } /** * Stop heartbeat */ stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } } /** * Send command to server */ sendCommand(command, params) { return __awaiter(this, void 0, void 0, function* () { if (this.connectionState !== client_types_1.ConnectionState.CONNECTED) { throw new Error('Not connected to server'); } if (!this.socket) { throw new Error('Socket not initialized'); } const id = (0, crypto_1.randomUUID)(); const request = { id, command, params, }; return new Promise((resolve, reject) => { const timeout = setTimeout(() => { this.pendingRequests.delete(id); reject(new Error('Request timeout')); }, this.options.timeout); this.pendingRequests.set(id, { resolve, reject, timeout, timestamp: Date.now(), }); try { const buffer = protocol_1.MessageFramer.encode(request); this.socket.write(buffer); } catch (error) { clearTimeout(timeout); this.pendingRequests.delete(id); reject(error); } }); }); } /** * Disconnect from server */ disconnect() { return __awaiter(this, void 0, void 0, function* () { // Prevent reconnection attempts this.reconnectAttempt = this.options.reconnectAttempts; this.stopHeartbeat(); if (this.socket && !this.socket.destroyed) { try { yield this.sendCommand(command_types_1.CommandType.DISCONNECT, {}); } catch (error) { // Ignore disconnect errors } // Clean up all socket listeners this.socket.removeAllListeners(); this.socket.end(); this.socket = null; } this.connectionState = client_types_1.ConnectionState.DISCONNECTED; }); } /** * Database API - mirrors AxioDB */ createDB(name) { return __awaiter(this, void 0, void 0, function* () { yield this.sendCommand(command_types_1.CommandType.CREATE_DB, { dbName: name }); return new DatabaseProxy_1.default(this, name); }); } deleteDatabase(name) { return __awaiter(this, void 0, void 0, function* () { yield this.sendCommand(command_types_1.CommandType.DELETE_DB, { dbName: name }); }); } isDatabaseExists(name) { return __awaiter(this, void 0, void 0, function* () { const result = yield this.sendCommand(command_types_1.CommandType.DB_EXISTS, { dbName: name }); return result.exists; }); } getInstanceInfo() { return __awaiter(this, void 0, void 0, function* () { return yield this.sendCommand(command_types_1.CommandType.GET_INSTANCE_INFO, {}); }); } /** * Get current connection state */ get state() { return this.connectionState; } /** * Check if connected */ get isConnected() { return this.connectionState === client_types_1.ConnectionState.CONNECTED; } } exports.AxioDBCloud = AxioDBCloud; //# sourceMappingURL=AxioDBCloud.client.js.map