@qdrant/js-client-rest
Version:
This repository contains the REST client for the [Qdrant](https://github.com/qdrant/qdrant) vector search engine.
1,364 lines (1,356 loc) • 123 kB
JavaScript
'use strict';
var none = {
isNone: function () {
return true;
},
orElse: function (fallback) {
return fallback;
},
orCall: function (getFallback) {
return getFallback();
},
orNull: function () {
return null;
},
orThrow: function (message) {
if (message === void 0) { message = 'Unexpected null value'; }
throw new TypeError(message);
},
map: function () {
return none;
},
get: function () {
return none;
}
};
var Some = /** @class */ (function () {
function Some(value) {
this.value = value;
}
Some.prototype.isNone = function () {
return false;
};
Some.prototype.orElse = function () {
return this.value;
};
Some.prototype.orCall = function () {
return this.value;
};
Some.prototype.orNull = function () {
return this.value;
};
Some.prototype.orThrow = function () {
return this.value;
};
Some.prototype.map = function (f) {
return maybe(f(this.value));
};
Some.prototype.get = function (key) {
return this.map(function (obj) { return obj[key]; });
};
return Some;
}());
function isMaybe(value) {
return value === none || value instanceof Some;
}
function maybe(value) {
if (isMaybe(value)) {
return value;
}
if (value == null) {
return none;
}
return some(value);
}
function some(value) {
if (value == null) {
throw new TypeError('some() does not accept null or undefined');
}
return new Some(value);
}
class ApiError extends Error {
constructor(response) {
super(response.statusText);
Object.setPrototypeOf(this, new.target.prototype);
this.headers = response.headers;
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.data = response.data;
}
}
let bigintReviver;
let bigintReplacer;
if ('rawJSON' in JSON) {
bigintReviver = function (_key, val, context) {
if (Number.isInteger(val) && !Number.isSafeInteger(val)) {
try {
return BigInt(context.source);
}
catch {
return val;
}
}
return val;
};
bigintReplacer = function (_key, val) {
if (typeof val === 'bigint') {
return JSON.rawJSON(String(val));
}
return val;
};
}
const sendBody = (method) => method === 'post' ||
method === 'put' ||
method === 'patch' ||
method === 'delete';
function queryString(params) {
const qs = [];
const encode = (key, value) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
Object.keys(params).forEach((key) => {
const value = params[key];
if (value != null) {
if (Array.isArray(value)) {
value.forEach((value) => qs.push(encode(key, value)));
}
else {
qs.push(encode(key, value));
}
}
});
if (qs.length > 0) {
return `?${qs.join('&')}`;
}
return '';
}
function getPath(path, payload) {
return path.replace(/\{([^}]+)\}/g, (_, key) => {
const value = encodeURIComponent(payload[key]);
delete payload[key];
return value;
});
}
function getQuery(method, payload, query) {
let queryObj = {};
if (sendBody(method)) {
query.forEach((key) => {
queryObj[key] = payload[key];
delete payload[key];
});
}
else {
queryObj = { ...payload };
}
return queryString(queryObj);
}
function getHeaders(body, init) {
const headers = new Headers(init);
if (body !== undefined &&
!(body instanceof FormData) &&
!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}
if (!headers.has('Accept')) {
headers.append('Accept', 'application/json');
}
return headers;
}
function getBody(method, payload) {
if (!sendBody(method)) {
return;
}
const body = payload instanceof FormData
? payload
: JSON.stringify(payload, bigintReplacer);
return method === 'delete' && body === '{}' ? undefined : body;
}
function mergeRequestInit(first, second) {
const headers = new Headers(first?.headers);
const other = new Headers(second?.headers);
for (const key of other.keys()) {
const value = other.get(key);
if (value != null) {
headers.set(key, value);
}
}
return { ...first, ...second, headers };
}
function getFetchParams(request) {
const payload = Object.assign(Array.isArray(request.payload) ? [] : {}, request.payload);
const path = getPath(request.path, payload);
const query = getQuery(request.method, payload, request.queryParams);
const body = getBody(request.method, payload);
const headers = sendBody(request.method)
? getHeaders(body, request.init?.headers)
: new Headers(request.init?.headers);
const url = request.baseUrl + path + query;
const init = {
...request.init,
method: request.method.toUpperCase(),
headers,
body,
};
return { url, init };
}
async function getResponseData(response) {
if (response.status === 204) {
return;
}
const contentType = response.headers.get('content-type');
const responseText = await response.text();
if (contentType && contentType.includes('application/json')) {
return JSON.parse(responseText, bigintReviver);
}
try {
return JSON.parse(responseText, bigintReviver);
}
catch (e) {
return responseText;
}
}
async function fetchJson(url, init) {
const response = await fetch(url, init);
const data = await getResponseData(response);
const result = {
headers: response.headers,
url: response.url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
data,
};
if (result.ok) {
return result;
}
throw new ApiError(result);
}
function wrapMiddlewares(middlewares, fetch) {
const handler = async (index, url, init) => {
if (middlewares == null || index === middlewares.length) {
return fetch(url, init);
}
const current = middlewares[index];
return await current(url, init, (nextUrl, nextInit) => handler(index + 1, nextUrl, nextInit));
};
return (url, init) => handler(0, url, init);
}
async function fetchUrl(request) {
const { url, init } = getFetchParams(request);
const response = await request.fetch(url, init);
return response;
}
function createFetch(fetch) {
const fun = async (payload, init) => {
try {
return await fetch(payload, init);
}
catch (err) {
if (err instanceof ApiError) {
throw new fun.Error(err);
}
throw err;
}
};
fun.Error = class extends ApiError {
constructor(error) {
super(error);
Object.setPrototypeOf(this, new.target.prototype);
}
getActualType() {
return {
status: this.status,
data: this.data,
};
}
};
return fun;
}
function fetcher() {
let baseUrl = '';
let defaultInit = {};
const middlewares = [];
const fetch = wrapMiddlewares(middlewares, fetchJson);
return {
configure: (config) => {
baseUrl = config.baseUrl || '';
defaultInit = config.init || {};
middlewares.splice(0);
middlewares.push(...(config.use || []));
},
use: (mw) => middlewares.push(mw),
path: (path) => ({
method: (method) => ({
create: ((queryParams) => createFetch((payload, init) => fetchUrl({
baseUrl: baseUrl || '',
path: path,
method: method,
queryParams: Object.keys(queryParams || {}),
payload,
init: mergeRequestInit(defaultInit, init),
fetch,
}))),
}),
}),
};
}
const Fetcher = {
for: () => fetcher(),
};
const MAX_CONTENT = 200;
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
Object.setPrototypeOf(this, new.target.prototype);
}
}
class QdrantClientUnexpectedResponseError extends CustomError {
static forResponse(response) {
const statusCodeStr = `${response.status}`;
const reasonPhraseStr = !response.statusText ? '(Unrecognized Status Code)' : `(${response.statusText})`;
const statusStr = `${statusCodeStr} ${reasonPhraseStr}`.trim();
const dataStr = response.data ? JSON.stringify(response.data, null, 2) : null;
let shortContent = '';
if (dataStr) {
shortContent = dataStr.length <= MAX_CONTENT ? dataStr : dataStr.slice(0, -4) + ' ...';
}
const rawContentStr = `Raw response content:\n${shortContent}`;
return new QdrantClientUnexpectedResponseError(`Unexpected Response: ${statusStr}\n${rawContentStr}`);
}
}
class QdrantClientConfigError extends CustomError {
}
class QdrantClientTimeoutError extends CustomError {
}
class QdrantClientResourceExhaustedError extends CustomError {
constructor(message, retryAfter) {
super(message);
const retryAfterNumber = Number(retryAfter);
if (isNaN(retryAfterNumber)) {
throw new CustomError(`Invalid retryAfter value: ${retryAfter}`);
}
this.retry_after = retryAfterNumber;
Object.setPrototypeOf(this, new.target.prototype);
}
}
// AUTOMATICALLY GENERATED FILE. DO NOT EDIT!
function createClientApi(client) {
return {
/** Create shard key */
createShardKey: client
.path('/collections/{collection_name}/shards')
.method('put')
.create({
timeout: true,
}),
/** Delete shard key */
deleteShardKey: client
.path('/collections/{collection_name}/shards/delete')
.method('post')
.create({
timeout: true,
}),
/**
* Returns information about the running Qdrant instance
* @description Returns information about the running Qdrant instance like version and commit id
*/
root: client
.path('/')
.method('get')
.create(),
/**
* Collect telemetry data
* @description Collect telemetry data including app info, system info, collections info, cluster info, configs and statistics
*/
telemetry: client
.path('/telemetry')
.method('get')
.create(),
/**
* Collect Prometheus metrics data
* @description Collect metrics data including app info, collections info, cluster info and statistics
*/
metrics: client
.path('/metrics')
.method('get')
.create(),
/**
* Get lock options
* @description Get lock options. If write is locked, all write operations and collection creation are forbidden
*/
getLocks: client
.path('/locks')
.method('get')
.create(),
/**
* Set lock options
* @description Set lock options. If write is locked, all write operations and collection creation are forbidden. Returns previous lock options
*/
postLocks: client
.path('/locks')
.method('post')
.create(),
/**
* Kubernetes healthz endpoint
* @description An endpoint for health checking used in Kubernetes.
*/
healthz: client
.path('/healthz')
.method('get')
.create(),
/**
* Kubernetes livez endpoint
* @description An endpoint for health checking used in Kubernetes.
*/
livez: client
.path('/livez')
.method('get')
.create(),
/**
* Kubernetes readyz endpoint
* @description An endpoint for health checking used in Kubernetes.
*/
readyz: client
.path('/readyz')
.method('get')
.create(),
/**
* Get issues
* @description Get a report of performance issues and configuration suggestions
*/
getIssues: client
.path('/issues')
.method('get')
.create(),
/**
* Clear issues
* @description Removes all issues reported so far
*/
clearIssues: client
.path('/issues')
.method('delete')
.create(),
/**
* Get cluster status info
* @description Get information about the current state and composition of the cluster
*/
clusterStatus: client
.path('/cluster')
.method('get')
.create(),
/** Tries to recover current peer Raft state. */
recoverCurrentPeer: client
.path('/cluster/recover')
.method('post')
.create(),
/**
* Remove peer from the cluster
* @description Tries to remove peer from the cluster. Will return an error if peer has shards on it.
*/
removePeer: client
.path('/cluster/peer/{peer_id}')
.method('delete')
.create({
force: true,
}),
/**
* List collections
* @description Get list name of all existing collections
*/
getCollections: client
.path('/collections')
.method('get')
.create(),
/**
* Collection info
* @description Get detailed information about specified existing collection
*/
getCollection: client
.path('/collections/{collection_name}')
.method('get')
.create(),
/**
* Create collection
* @description Create new collection with given parameters
*/
createCollection: client
.path('/collections/{collection_name}')
.method('put')
.create({
timeout: true,
}),
/**
* Delete collection
* @description Drop collection and all associated data
*/
deleteCollection: client
.path('/collections/{collection_name}')
.method('delete')
.create({
timeout: true,
}),
/**
* Update collection parameters
* @description Update parameters of the existing collection
*/
updateCollection: client
.path('/collections/{collection_name}')
.method('patch')
.create({
timeout: true,
}),
/** Update aliases of the collections */
updateAliases: client
.path('/collections/aliases')
.method('post')
.create({
timeout: true,
}),
/**
* Create index for field in collection
* @description Create index for field in collection
*/
createFieldIndex: client
.path('/collections/{collection_name}/index')
.method('put')
.create({
wait: true,
ordering: true,
}),
/**
* Check the existence of a collection
* @description Returns "true" if the given collection name exists, and "false" otherwise
*/
collectionExists: client
.path('/collections/{collection_name}/exists')
.method('get')
.create(),
/**
* Delete index for field in collection
* @description Delete field index for collection
*/
deleteFieldIndex: client
.path('/collections/{collection_name}/index/{field_name}')
.method('delete')
.create({
wait: true,
ordering: true,
}),
/**
* Collection cluster info
* @description Get cluster information for a collection
*/
collectionClusterInfo: client
.path('/collections/{collection_name}/cluster')
.method('get')
.create(),
/** Update collection cluster setup */
updateCollectionCluster: client
.path('/collections/{collection_name}/cluster')
.method('post')
.create({
timeout: true,
}),
/**
* List aliases for collection
* @description Get list of all aliases for a collection
*/
getCollectionAliases: client
.path('/collections/{collection_name}/aliases')
.method('get')
.create(),
/**
* List collections aliases
* @description Get list of all existing collections aliases
*/
getCollectionsAliases: client
.path('/aliases')
.method('get')
.create(),
/**
* Recover from an uploaded snapshot
* @description Recover local collection data from an uploaded snapshot. This will overwrite any data, stored on this node, for the collection. If collection does not exist - it will be created.
*/
recoverFromUploadedSnapshot: client
.path('/collections/{collection_name}/snapshots/upload')
.method('post')
.create({
wait: true,
priority: true,
checksum: true,
}),
/**
* Recover from a snapshot
* @description Recover local collection data from a snapshot. This will overwrite any data, stored on this node, for the collection. If collection does not exist - it will be created.
*/
recoverFromSnapshot: client
.path('/collections/{collection_name}/snapshots/recover')
.method('put')
.create({
wait: true,
}),
/**
* List collection snapshots
* @description Get list of snapshots for a collection
*/
listSnapshots: client
.path('/collections/{collection_name}/snapshots')
.method('get')
.create(),
/**
* Create collection snapshot
* @description Create new snapshot for a collection
*/
createSnapshot: client
.path('/collections/{collection_name}/snapshots')
.method('post')
.create({
wait: true,
}),
/**
* Download collection snapshot
* @description Download specified snapshot from a collection as a file
*/
getSnapshot: client
.path('/collections/{collection_name}/snapshots/{snapshot_name}')
.method('get')
.create(),
/**
* Delete collection snapshot
* @description Delete snapshot for a collection
*/
deleteSnapshot: client
.path('/collections/{collection_name}/snapshots/{snapshot_name}')
.method('delete')
.create({
wait: true,
}),
/**
* List of storage snapshots
* @description Get list of snapshots of the whole storage
*/
listFullSnapshots: client
.path('/snapshots')
.method('get')
.create(),
/**
* Create storage snapshot
* @description Create new snapshot of the whole storage
*/
createFullSnapshot: client
.path('/snapshots')
.method('post')
.create({
wait: true,
}),
/**
* Download storage snapshot
* @description Download specified snapshot of the whole storage as a file
*/
getFullSnapshot: client
.path('/snapshots/{snapshot_name}')
.method('get')
.create(),
/**
* Delete storage snapshot
* @description Delete snapshot of the whole storage
*/
deleteFullSnapshot: client
.path('/snapshots/{snapshot_name}')
.method('delete')
.create({
wait: true,
}),
/**
* Recover shard from an uploaded snapshot
* @description Recover shard of a local collection from an uploaded snapshot. This will overwrite any data, stored on this node, for the collection shard.
*/
recoverShardFromUploadedSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/upload')
.method('post')
.create({
wait: true,
priority: true,
checksum: true,
}),
/**
* Recover from a snapshot
* @description Recover shard of a local collection data from a snapshot. This will overwrite any data, stored in this shard, for the collection.
*/
recoverShardFromSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/recover')
.method('put')
.create({
wait: true,
}),
/**
* List shards snapshots for a collection
* @description Get list of snapshots for a shard of a collection
*/
listShardSnapshots: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots')
.method('get')
.create(),
/**
* Create shard snapshot
* @description Create new snapshot of a shard for a collection
*/
createShardSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots')
.method('post')
.create({
wait: true,
}),
/**
* Download collection snapshot
* @description Download specified snapshot of a shard from a collection as a file
*/
getShardSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/{snapshot_name}')
.method('get')
.create(),
/**
* Delete shard snapshot
* @description Delete snapshot of a shard for a collection
*/
deleteShardSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/{snapshot_name}')
.method('delete')
.create({
wait: true,
}),
/**
* Get point
* @description Retrieve full information of single point by id
*/
getPoint: client
.path('/collections/{collection_name}/points/{id}')
.method('get')
.create(),
/**
* Upsert points
* @description Perform insert + updates on points. If point with given ID already exists - it will be overwritten.
*/
upsertPoints: client
.path('/collections/{collection_name}/points')
.method('put')
.create({
wait: true,
ordering: true,
}),
/**
* Get points
* @description Retrieve multiple points by specified IDs
*/
getPoints: client
.path('/collections/{collection_name}/points')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Delete points
* @description Delete points
*/
deletePoints: client
.path('/collections/{collection_name}/points/delete')
.method('post')
.create({
wait: true,
ordering: true,
}),
/**
* Update vectors
* @description Update specified named vectors on points, keep unspecified vectors intact.
*/
updateVectors: client
.path('/collections/{collection_name}/points/vectors')
.method('put')
.create({
wait: true,
ordering: true,
}),
/**
* Delete vectors
* @description Delete named vectors from the given points.
*/
deleteVectors: client
.path('/collections/{collection_name}/points/vectors/delete')
.method('post')
.create({
wait: true,
ordering: true,
}),
/**
* Overwrite payload
* @description Replace full payload of points with new one
*/
overwritePayload: client
.path('/collections/{collection_name}/points/payload')
.method('put')
.create({
wait: true,
ordering: true,
}),
/**
* Set payload
* @description Set payload values for points
*/
setPayload: client
.path('/collections/{collection_name}/points/payload')
.method('post')
.create({
wait: true,
ordering: true,
}),
/**
* Delete payload
* @description Delete specified key payload for points
*/
deletePayload: client
.path('/collections/{collection_name}/points/payload/delete')
.method('post')
.create({
wait: true,
ordering: true,
}),
/**
* Clear payload
* @description Remove all payload for specified points
*/
clearPayload: client
.path('/collections/{collection_name}/points/payload/clear')
.method('post')
.create({
wait: true,
ordering: true,
}),
/**
* Batch update points
* @description Apply a series of update operations for points, vectors and payloads
*/
batchUpdate: client
.path('/collections/{collection_name}/points/batch')
.method('post')
.create({
wait: true,
ordering: true,
}),
/**
* Scroll points
* @description Scroll request - paginate over all points which matches given filtering condition
*/
scrollPoints: client
.path('/collections/{collection_name}/points/scroll')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Search points
* @deprecated
* @description Retrieve closest points based on vector similarity and given filtering conditions
*/
searchPoints: client
.path('/collections/{collection_name}/points/search')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Search batch points
* @deprecated
* @description Retrieve by batch the closest points based on vector similarity and given filtering conditions
*/
searchBatchPoints: client
.path('/collections/{collection_name}/points/search/batch')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Search point groups
* @deprecated
* @description Retrieve closest points based on vector similarity and given filtering conditions, grouped by a given payload field
*/
searchPointGroups: client
.path('/collections/{collection_name}/points/search/groups')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Recommend points
* @deprecated
* @description Look for the points which are closer to stored positive examples and at the same time further to negative examples.
*/
recommendPoints: client
.path('/collections/{collection_name}/points/recommend')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Recommend batch points
* @deprecated
* @description Look for the points which are closer to stored positive examples and at the same time further to negative examples.
*/
recommendBatchPoints: client
.path('/collections/{collection_name}/points/recommend/batch')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Recommend point groups
* @deprecated
* @description Look for the points which are closer to stored positive examples and at the same time further to negative examples, grouped by a given payload field.
*/
recommendPointGroups: client
.path('/collections/{collection_name}/points/recommend/groups')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Discover points
* @deprecated
* @description Use context and a target to find the most similar points to the target, constrained by the context.
* When using only the context (without a target), a special search - called context search - is performed where pairs of points are used to generate a loss that guides the search towards the zone where most positive examples overlap. This means that the score minimizes the scenario of finding a point closer to a negative than to a positive part of a pair.
* Since the score of a context relates to loss, the maximum score a point can get is 0.0, and it becomes normal that many points can have a score of 0.0.
* When using target (with or without context), the score behaves a little different: The integer part of the score represents the rank with respect to the context, while the decimal part of the score relates to the distance to the target. The context part of the score for each pair is calculated +1 if the point is closer to a positive than to a negative part of a pair, and -1 otherwise.
*/
discoverPoints: client
.path('/collections/{collection_name}/points/discover')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Discover batch points
* @deprecated
* @description Look for points based on target and/or positive and negative example pairs, in batch.
*/
discoverBatchPoints: client
.path('/collections/{collection_name}/points/discover/batch')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Count points
* @description Count points which matches given filtering condition
*/
countPoints: client
.path('/collections/{collection_name}/points/count')
.method('post')
.create({
timeout: true,
}),
/**
* Facet a payload key with a given filter.
* @description Count points that satisfy the given filter for each unique value of a payload key.
*/
facet: client
.path('/collections/{collection_name}/facet')
.method('post')
.create({
timeout: true,
consistency: true,
}),
/**
* Query points
* @description Universally query points. This endpoint covers all capabilities of search, recommend, discover, filters. But also enables hybrid and multi-stage queries.
*/
queryPoints: client
.path('/collections/{collection_name}/points/query')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Query points in batch
* @description Universally query points in batch. This endpoint covers all capabilities of search, recommend, discover, filters. But also enables hybrid and multi-stage queries.
*/
queryBatchPoints: client
.path('/collections/{collection_name}/points/query/batch')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Query points, grouped by a given payload field
* @description Universally query points, grouped by a given payload field
*/
queryPointsGroups: client
.path('/collections/{collection_name}/points/query/groups')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Search points matrix distance pairs
* @description Compute distance matrix for sampled points with a pair based output format
*/
searchMatrixPairs: client
.path('/collections/{collection_name}/points/search/matrix/pairs')
.method('post')
.create({
consistency: true,
timeout: true,
}),
/**
* Search points matrix distance offsets
* @description Compute distance matrix for sampled points with an offset based output format
*/
searchMatrixOffsets: client
.path('/collections/{collection_name}/points/search/matrix/offsets')
.method('post')
.create({
consistency: true,
timeout: true,
}),
};
}
function createApis(baseUrl, args) {
const client = createClient(baseUrl, args);
return createClientApi(client);
}
function createClient(baseUrl, { headers, timeout, connections }) {
const use = [];
if (Number.isFinite(timeout)) {
use.push(async (url, init, next) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
return await next(url, Object.assign(init, { signal: controller.signal }));
}
catch (e) {
if (e instanceof Error && e.name === 'AbortError') {
throw new QdrantClientTimeoutError(e.message);
}
throw e;
}
finally {
clearTimeout(id);
}
});
}
use.push(async (url, init, next) => {
let response;
try {
response = await next(url, init);
if (response.status === 200 || response.status === 201) {
return response;
}
}
catch (error) {
if (error instanceof ApiError && error.status === 429) {
const retryAfterHeader = error.headers.get('retry-after')?.[0];
if (retryAfterHeader) {
throw new QdrantClientResourceExhaustedError(error.message, retryAfterHeader);
}
}
throw error;
}
throw QdrantClientUnexpectedResponseError.forResponse(response);
});
const client = Fetcher.for();
// Configure client with 'undici' agent which is used in Node 18+
client.configure({
baseUrl,
init: {
headers,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
dispatcher: undefined,
},
use,
});
return client;
}
const PACKAGE_VERSION = '1.15.0';
const ClientVersion = {
/**
* Parses a version string into a structured Version object.
* @param version - The version string to parse (e.g., "1.2.3").
* @returns A Version object.
* @throws If the version format is invalid.
*/
parseVersion(version) {
if (!version) {
throw new Error('Version is null');
}
let major = undefined;
let minor = undefined;
[major, minor] = version.split('.', 2);
major = parseInt(major, 10);
minor = parseInt(minor, 10);
if (isNaN(major) || isNaN(minor)) {
throw new Error(`Unable to parse version, expected format: x.y[.z], found: ${version}`);
}
return {
major,
minor,
};
},
/**
* Checks if the client version is compatible with the server version.
* @param clientVersion - The client version string.
* @param serverVersion - The server version string.
* @returns True if compatible, otherwise false.
*/
isCompatible(clientVersion, serverVersion) {
if (!clientVersion || !serverVersion) {
console.debug(`Unable to compare versions with null values. Client: ${clientVersion}, Server: ${serverVersion}`);
return false;
}
if (clientVersion === serverVersion)
return true;
try {
const client = ClientVersion.parseVersion(clientVersion);
const server = ClientVersion.parseVersion(serverVersion);
return client.major === server.major && Math.abs(client.minor - server.minor) <= 1;
}
catch (error) {
console.debug(`Unable to compare versions: ${error}`);
return false;
}
},
};
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
class QdrantClient {
constructor({ url, host, apiKey, https, prefix, port = 6333, timeout = 300000, checkCompatibility = true, ...args } = {}) {
this._https = https ?? typeof apiKey === 'string';
this._scheme = this._https ? 'https' : 'http';
this._prefix = prefix ?? '';
if (this._prefix.length > 0 && !this._prefix.startsWith('/')) {
this._prefix = `/${this._prefix}`;
}
if (url && host) {
throw new QdrantClientConfigError(`Only one of \`url\`, \`host\` params can be set. Url is ${url}, host is ${host}`);
}
if (host && (host.startsWith('http://') || host.startsWith('https://') || /:\d+$/.test(host))) {
throw new QdrantClientConfigError('The `host` param is not expected to contain neither protocol (http:// or https://) nor port (:6333).\n' +
'Try to use the `url` parameter instead.');
}
else if (url) {
if (!(url.startsWith('http://') || url.startsWith('https://'))) {
throw new QdrantClientConfigError('The `url` param expected to contain a valid URL starting with a protocol (http:// or https://).');
}
const parsedUrl = new URL(url);
this._host = parsedUrl.hostname;
this._port = parsedUrl.port ? Number(parsedUrl.port) : port;
this._scheme = parsedUrl.protocol.replace(':', '');
if (this._prefix.length > 0 && parsedUrl.pathname !== '/') {
throw new QdrantClientConfigError('Prefix can be set either in `url` or in `prefix`.\n' +
`url is ${url}, prefix is ${parsedUrl.pathname}`);
}
}
else {
this._port = port;
this._host = host ?? '127.0.0.1';
}
const headers = new Headers([['user-agent', 'qdrant-js/' + String(PACKAGE_VERSION)]]);
const metadata = args.headers ?? {};
Object.keys(metadata).forEach((field) => {
if (metadata[field]) {
headers.set(field, String(metadata[field]));
}
});
if (typeof apiKey === 'string') {
if (this._scheme === 'http') {
console.warn('Api key is used with unsecure connection.');
}
headers.set('api-key', apiKey);
}
const address = this._port ? `${this._host}:${this._port}` : this._host;
this._restUri = `${this._scheme}://${address}${this._prefix}`;
const connections = args.maxConnections;
const restArgs = { headers, timeout, connections };
this._openApiClient = createApis(this._restUri, restArgs);
if (checkCompatibility) {
this._openApiClient
.root({})
.then((response) => {
const serverVersion = response.data.version;
if (!ClientVersion.isCompatible(PACKAGE_VERSION, serverVersion)) {
console.warn(`Client version ${PACKAGE_VERSION} is incompatible with server version ${serverVersion}. Major versions should match and minor version difference must not exceed 1. Set checkCompatibility=false to skip version check.`);
}
})
.catch(() => {
console.warn(`Failed to obtain server version. Unable to check client-server compatibility. Set checkCompatibility=false to skip version check.`);
});
}
}
/**
* API getter
*
* @returns An instance of an API, generated from OpenAPI schema.
*/
api() {
return this._openApiClient;
}
/**
* Search for points in multiple collections
*
* @param collectionName Name of the collection
* @param {object} args -
* - searches: List of search requests
* - consistency: Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* number - number of replicas to query, values should present in all queried replicas
* 'majority' - query all replicas, but return values present in the majority of replicas
* 'quorum' - query the majority of replicas, return values present in all of them
* 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @returns List of search responses
*/
async searchBatch(collection_name, { searches, consistency, timeout, }) {
const response = await this._openApiClient.searchBatchPoints({
collection_name,
consistency,
timeout,
searches,
});
return maybe(response.data.result).orThrow('Search batch returned empty');
}
/**
* Search for closest vectors in collection taking into account filtering conditions
*
* @param collection_name Collection to search in
* @param {object} args -
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* - vector:
* Search for vectors closest to this.
* Can be either a vector itself, or a named vector, or a tuple of vector name and vector itself
* - filter:
* - Exclude vectors which doesn't fit given conditions.
* - If `None` - search among all vectors
* - params: Additional search params
* - limit: How many results return
* - offset:
* Offset of the first result to return.
* May be used to paginate results.
* Note: large offset values may cause performance issues.
* - with_payload:
* - Specify which stored payload should be attached to the result.
* - If `True` - attach all payload
* - If `False` - do not attach any payload
* - If List of string - include only specified fields
* - If `PayloadSelector` - use explicit rules
* - with_vector:
* - If `True` - Attach stored vector to the search result.
* - If `False` - Do not attach vector.
* - If List of string - include only specified fields
* - Default: `False`
* - score_threshold:
* Define a minimal score threshold for the result.
* If defined, less similar results will not be returned.
* Score of the returned result might be higher or smaller than the threshold depending
* on the Distance function used.
* E.g. for cosine similarity only higher scores will be returned.
* - consistency:
* Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* - int - number of replicas to query, values should present in all queried replicas
* - 'majority' - query all replicas, but return values present in the majority of replicas
* - 'quorum' - query the majority of replicas, return values present in all of them
* - 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @example
* // Search with filter
* client.search(
* "test_collection",
* {
* vector: [1.0, 0.1, 0.2, 0.7],
* filter: {
* must: [
* {
* key: 'color',
* range: {
* color: 'red'
* }
* }
* ]
* )
* }
* )
* @returns List of found close points with similarity scores.
*/
async search(collection_name, { shard_key, vector, limit = 10, offset = 0, filter, params, with_payload = true, with_vector = false, score_threshold, consistency, timeout, }) {
const response = await this._openApiClient.searchPoints({
collection_name,
consistency,
timeout,
shard_key,
vector,
limit,
offset,
filter,
params,
with_payload,
with_vector,
score_threshold,
});
return maybe(response.data.result).orThrow('Search returned empty');
}
/**
* Perform multiple recommend requests in batch mode
* @param collection_name Name of the collection
* @param {object} args
* - searches: List of recommend requests
* - consistency:
* Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* - number - number of replicas to query, values should present in all queried replicas
* - 'majority' - query all replicas, but return values present in the majority of replicas
* - 'quorum' - query the majority of replicas, return values present in all of them
* - 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @returns List of recommend responses
*/
async recommendBatch(collection_name, { searches, consistency, timeout, }) {
const response = await this._openApiClient.recommendBatchPoints({
collection_name,
searches,
consistency,
timeout,
});
return maybe(response.data.result).orElse([]);
}
/**
* @alias recommendBatch
*/
async recommend_batch(collection_name, { searches, consistency, timeout, }) {
const response = await this._openApiClient.recommendBatchPoints({
collection_name,
searches,
consistency,
timeout,
});
return maybe(response.data.result).orElse([]);
}
/**
* Recommendation request. Provides positive and negative examples of the vectors,
* which can be ids of points that are already stored in the collection, raw vectors, or even ids and vectors combined.
* Service should look for the points which are closer to positive examples and at the same time further to negative examples.
* The concrete way of how to compare negative and positive distances is up to the `strategy` chosen.
* @param collection_name Collection to search in
* @param {object} args
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* - positive:
* List of stored point IDs, which should be used as reference for similarity search.
* If there is only one ID provided - this req