@monkdb/monkdb
Version:
🚀 Official TypeScript SDK for MonkDB — a unified, AI-native database for diverse data workloads
139 lines • 5.7 kB
JavaScript
// --- 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