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