UNPKG

@monkdb/monkdb

Version:

🚀 Official TypeScript SDK for MonkDB — a unified, AI-native database for diverse data workloads

139 lines • 5.7 kB
// --- src/client/MonkClient.ts --- import { MonkServer } from './MonkServer.js'; import { MonkProgrammingError, MonkConnectionError } from '../errors/MonkErrors.js'; export class MonkClient { constructor(options = {}) { this.servers = []; this.serverMap = new Map(); this.activeServers = []; this.inactiveServers = []; this.currentIndex = 0; const { servers = ['http://127.0.0.1:4200'], retries = 3, backoffFactor = 100, retryInterval = 30000, path = '/_sql?types=true', errorTrace = false, ...serverOptions } = options; const fullPath = errorTrace ? `${path}&error_trace=true` : path; this.path = fullPath; this.retries = retries; this.backoffFactor = backoffFactor; this.retryInterval = retryInterval; for (const url of servers) { const server = new MonkServer(url, serverOptions); this.servers.push(server); this.serverMap.set(url, server); this.activeServers.push(url); } } rotate() { this.restoreInactiveServers(); if (this.activeServers.length === 0) { if (this.inactiveServers.length === 0) { throw new MonkConnectionError('No MonkDB servers available'); } else { const revived = this.inactiveServers.shift(); this.activeServers.push(revived.url); } } const url = this.activeServers[this.currentIndex]; this.currentIndex = (this.currentIndex + 1) % this.activeServers.length; return this.serverMap.get(url); } async sql(stmt, args, bulkArgs) { if (!stmt) throw new MonkProgrammingError('SQL statement cannot be empty'); const body = JSON.stringify(bulkArgs ? { stmt, bulk_args: bulkArgs } : args ? { stmt, args } : { stmt }); return await this.requestWithRetries(() => { const server = this.rotate(); return server.sendRequest('POST', this.path, Buffer.from(body)); }); } async request(method, path, body, headers = {}) { const server = this.rotate(); return server.sendRequest(method, path, body, headers); } async requestWithRetries(fn) { let lastError = null; for (let attempt = 0; attempt < this.retries; attempt++) { try { const response = await fn(); // Retry on 5xx if (response.status >= 500) { lastError = new MonkConnectionError(`MonkDB server responded with ${response.status}: Retrying`); // wait and retry if (attempt < this.retries - 1) { await this.sleep(this.backoffFactor * Math.pow(2, attempt)); continue; } else { break; } } // Handle redirects (3xx) if (this.isRedirect(response.status) && response.headers['location']) { const redirectUrl = response.headers['location']; if (typeof redirectUrl === 'string') { const newUrl = redirectUrl; if (!this.serverMap.has(newUrl)) { const redirectServer = new MonkServer(newUrl); this.servers.push(redirectServer); this.serverMap.set(newUrl, redirectServer); this.activeServers.push(newUrl); } continue; // re-attempt with redirect target } } // Successful response if (response.status >= 200 && response.status < 300) { return response.data; } // Other non-5xx errors: Treat as fatal throw new MonkProgrammingError(`MonkDB responded with status ${response.status}`); } catch (err) { lastError = err; this.dropCurrentServer(); // Retry if not last attempt if (attempt < this.retries - 1) { await this.sleep(this.backoffFactor * Math.pow(2, attempt)); } } } throw new MonkConnectionError(`All servers failed. Last error: ${lastError?.message || 'Unknown error'}`); } dropCurrentServer() { if (this.activeServers.length === 0) return; const failed = this.activeServers.splice(this.currentIndex, 1)[0]; this.inactiveServers.push({ url: failed, ts: Date.now() }); if (this.currentIndex >= this.activeServers.length) { this.currentIndex = 0; } } restoreInactiveServers() { const now = Date.now(); const stillInactive = []; for (const entry of this.inactiveServers) { if (now - entry.ts >= this.retryInterval) { this.activeServers.push(entry.url); } else { stillInactive.push(entry); } } this.inactiveServers = stillInactive; } isRedirect(status) { return status >= 300 && status < 400; } sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } close() { this.servers = []; this.serverMap.clear(); this.activeServers = []; this.inactiveServers = []; } toString() { return `<MonkClient ${this.activeServers.join(', ')}>`; } } //# sourceMappingURL=MonkClient.js.map