UNPKG

d-vecdb

Version:

TypeScript/JavaScript client for d-vecDB - High-performance vector database with persistent metadata, WAL corruption protection and GPU acceleration

339 lines 12.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RestClient = void 0; const axios_1 = __importDefault(require("axios")); const exceptions_1 = require("./exceptions"); /** * REST client for d-vecDB */ class RestClient { constructor(config = {}) { const { host = 'localhost', port = 8080, timeout = 30000, secure = false, apiKey, } = config; const protocol = secure ? 'https' : 'http'; this.baseUrl = `${protocol}://${host}:${port}`; this.client = axios_1.default.create({ baseURL: this.baseUrl, timeout, headers: { 'Content-Type': 'application/json', ...(apiKey && { Authorization: `Bearer ${apiKey}` }), }, }); // Add response interceptor for error handling this.client.interceptors.response.use(response => response, error => { throw this.handleError(error); }); } /** * Unwrap the server's standard response format * Server returns: {"success": true, "data": <actual_data>, "error": null} */ unwrapResponse(responseData) { // If the response has the standard wrapper format, extract the data if ('success' in responseData && 'data' in responseData) { if (responseData.error) { throw new exceptions_1.ServerError(responseData.error); } return responseData.data; } // Otherwise, return the data as-is (for backward compatibility) return responseData; } /** * Handle axios errors and convert to custom exceptions */ handleError(error) { if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') { return new exceptions_1.ConnectionError(`Failed to connect to server at ${this.baseUrl}`); } if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { return new exceptions_1.TimeoutError('Request timed out'); } if (error.response) { const status = error.response.status; const message = error.response.data?.message || error.message; switch (status) { case 401: return new exceptions_1.AuthenticationError(message); case 404: if (message.toLowerCase().includes('collection')) { const match = message.match(/Collection '([^']+)'/); const collectionName = match ? match[1] : 'unknown'; return new exceptions_1.CollectionNotFoundError(collectionName); } if (message.toLowerCase().includes('vector')) { const match = message.match(/Vector '([^']+)'/); const vectorId = match ? match[1] : 'unknown'; return new exceptions_1.VectorNotFoundError(vectorId); } return new exceptions_1.ServerError(message, status); case 409: if (message.toLowerCase().includes('already exists')) { const match = message.match(/Collection '([^']+)'/); const collectionName = match ? match[1] : 'unknown'; return new exceptions_1.CollectionExistsError(collectionName); } return new exceptions_1.ServerError(message, status); case 400: return new exceptions_1.InvalidParameterError(message); case 500: case 502: case 503: case 504: return new exceptions_1.ServerError(message, status); default: return new exceptions_1.ServerError(message, status); } } return new exceptions_1.VectorDBError(error.message); } /** * Create a new collection */ async createCollection(config) { const response = await this.client.post('/collections', { name: config.name, dimension: config.dimension, distance_metric: config.distanceMetric, vector_type: config.vectorType, index_config: config.indexConfig ? { max_connections: config.indexConfig.maxConnections, ef_construction: config.indexConfig.efConstruction, ef_search: config.indexConfig.efSearch, max_layer: config.indexConfig.maxLayer, } : undefined, }); const data = this.unwrapResponse(response.data); return this.transformCollectionResponse(data); } /** * List all collection names (fast - no details) */ async listCollectionNames() { const response = await this.client.get('/collections'); return this.unwrapResponse(response.data); } /** * List all collections with full details (slower - fetches each collection) */ async listCollections() { const collectionNames = await this.listCollectionNames(); // Fetch details for each collection const collections = []; for (const name of collectionNames) { try { const collectionResponse = await this.getCollection(name); collections.push(collectionResponse.collection); } catch (error) { // If a collection can't be fetched, skip it but continue with others console.warn(`Failed to fetch details for collection '${name}':`, error); } } return { collections }; } /** * Get collection information */ async getCollection(name) { const response = await this.client.get(`/collections/${name}`); const data = this.unwrapResponse(response.data); return this.transformCollectionResponse(data); } /** * Get collection statistics */ async getCollectionStats(name) { // Note: Server returns both config and stats from /collections/:collection endpoint const response = await this.client.get(`/collections/${name}`); const data = this.unwrapResponse(response.data); // Extract stats from the tuple response [config, stats] const stats = Array.isArray(data) ? data[1] : data; return { name: stats.name, vectorCount: stats.vector_count, dimension: stats.dimension, indexSize: stats.index_size, memoryUsage: stats.memory_usage, }; } /** * Delete a collection */ async deleteCollection(name) { const response = await this.client.delete(`/collections/${name}`); const data = this.unwrapResponse(response.data); return this.transformCollectionResponse(data); } /** * Insert a single vector */ async insertVector(collectionName, vector) { const response = await this.client.post(`/collections/${collectionName}/vectors`, { id: vector.id, data: Array.from(vector.data), metadata: vector.metadata, }); const data = this.unwrapResponse(response.data); return { success: true, message: data.message, count: 1, }; } /** * Batch insert vectors */ async batchInsert(collectionName, vectors) { const response = await this.client.post(`/collections/${collectionName}/vectors/batch`, { vectors: vectors.map(v => ({ id: v.id, data: Array.from(v.data), metadata: v.metadata, })), }); const data = this.unwrapResponse(response.data); return { success: true, message: data.message, count: vectors.length, }; } /** * Get a vector by ID */ async getVector(collectionName, vectorId) { const response = await this.client.get(`/collections/${collectionName}/vectors/${vectorId}`); const data = this.unwrapResponse(response.data); return { id: data.id, data: data.data, metadata: data.metadata, }; } /** * Update a vector */ async updateVector(collectionName, vector) { const response = await this.client.put(`/collections/${collectionName}/vectors/${vector.id}`, { data: Array.from(vector.data), metadata: vector.metadata, }); const data = this.unwrapResponse(response.data); return { success: true, message: data.message, }; } /** * Delete a vector */ async deleteVector(collectionName, vectorId) { const response = await this.client.delete(`/collections/${collectionName}/vectors/${vectorId}`); const data = this.unwrapResponse(response.data); return { success: true, message: data.message, }; } /** * Search for similar vectors */ async search(request) { const response = await this.client.post(`/collections/${request.collectionName}/search`, { query_vector: Array.from(request.queryVector), limit: request.limit || 10, ef_search: request.efSearch, filter: request.filter, }); const data = this.unwrapResponse(response.data); return { results: (Array.isArray(data) ? data : []).map(r => ({ id: r.id, distance: r.distance, metadata: r.metadata, })), }; } /** * Get server statistics */ async getStats() { const response = await this.client.get('/stats'); const data = this.unwrapResponse(response.data); return { totalVectors: data.total_vectors, totalCollections: data.total_collections, memoryUsage: data.memory_usage, diskUsage: data.disk_usage, uptimeSeconds: data.uptime_seconds, }; } /** * Health check */ async health() { const response = await this.client.get('/health'); const data = this.unwrapResponse(response.data); return { status: data.status, details: data.details, }; } /** * Ping the server */ async ping() { try { await this.health(); return true; } catch { return false; } } /** * Transform collection response from API format to client format */ transformCollectionResponse(data) { // Server returns tuple: [CollectionConfig, CollectionStats] if (Array.isArray(data) && data.length === 2) { const [config] = data; return { collection: this.transformCollectionInfo(config), message: undefined, }; } // Fallback for other response formats (e.g., creation responses) const d = data; return { collection: this.transformCollectionInfo(d.collection || d), message: d.message, }; } /** * Transform collection info from API format to client format */ transformCollectionInfo(data) { const d = data; return { name: d.name, dimension: d.dimension, distanceMetric: d.distance_metric, vectorType: d.vector_type, indexConfig: { maxConnections: d.index_config?.max_connections, efConstruction: d.index_config?.ef_construction, efSearch: d.index_config?.ef_search, maxLayer: d.index_config?.max_layer, }, }; } } exports.RestClient = RestClient; //# sourceMappingURL=rest-client.js.map