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
JavaScript
"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