@functional-systems/lambdadb 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +42 -0
  2. package/dist/commonjs/compat/qdrant/client.d.ts +210 -0
  3. package/dist/commonjs/compat/qdrant/client.d.ts.map +1 -0
  4. package/dist/commonjs/compat/qdrant/client.js +573 -0
  5. package/dist/commonjs/compat/qdrant/client.js.map +1 -0
  6. package/dist/commonjs/compat/qdrant/conversions.d.ts +40 -0
  7. package/dist/commonjs/compat/qdrant/conversions.d.ts.map +1 -0
  8. package/dist/commonjs/compat/qdrant/conversions.js +386 -0
  9. package/dist/commonjs/compat/qdrant/conversions.js.map +1 -0
  10. package/dist/commonjs/compat/qdrant/errors.d.ts +10 -0
  11. package/dist/commonjs/compat/qdrant/errors.d.ts.map +1 -0
  12. package/dist/commonjs/compat/qdrant/errors.js +25 -0
  13. package/dist/commonjs/compat/qdrant/errors.js.map +1 -0
  14. package/dist/commonjs/compat/qdrant/filters.d.ts +10 -0
  15. package/dist/commonjs/compat/qdrant/filters.d.ts.map +1 -0
  16. package/dist/commonjs/compat/qdrant/filters.js +172 -0
  17. package/dist/commonjs/compat/qdrant/filters.js.map +1 -0
  18. package/dist/commonjs/compat/qdrant/models.d.ts +198 -0
  19. package/dist/commonjs/compat/qdrant/models.d.ts.map +1 -0
  20. package/dist/commonjs/compat/qdrant/models.js +205 -0
  21. package/dist/commonjs/compat/qdrant/models.js.map +1 -0
  22. package/dist/commonjs/compat/qdrant.d.ts +4 -0
  23. package/dist/commonjs/compat/qdrant.d.ts.map +1 -0
  24. package/dist/commonjs/compat/qdrant.js +45 -0
  25. package/dist/commonjs/compat/qdrant.js.map +1 -0
  26. package/dist/esm/compat/qdrant/client.d.ts +210 -0
  27. package/dist/esm/compat/qdrant/client.d.ts.map +1 -0
  28. package/dist/esm/compat/qdrant/client.js +536 -0
  29. package/dist/esm/compat/qdrant/client.js.map +1 -0
  30. package/dist/esm/compat/qdrant/conversions.d.ts +40 -0
  31. package/dist/esm/compat/qdrant/conversions.d.ts.map +1 -0
  32. package/dist/esm/compat/qdrant/conversions.js +336 -0
  33. package/dist/esm/compat/qdrant/conversions.js.map +1 -0
  34. package/dist/esm/compat/qdrant/errors.d.ts +10 -0
  35. package/dist/esm/compat/qdrant/errors.d.ts.map +1 -0
  36. package/dist/esm/compat/qdrant/errors.js +19 -0
  37. package/dist/esm/compat/qdrant/errors.js.map +1 -0
  38. package/dist/esm/compat/qdrant/filters.d.ts +10 -0
  39. package/dist/esm/compat/qdrant/filters.d.ts.map +1 -0
  40. package/dist/esm/compat/qdrant/filters.js +169 -0
  41. package/dist/esm/compat/qdrant/filters.js.map +1 -0
  42. package/dist/esm/compat/qdrant/models.d.ts +198 -0
  43. package/dist/esm/compat/qdrant/models.d.ts.map +1 -0
  44. package/dist/esm/compat/qdrant/models.js +185 -0
  45. package/dist/esm/compat/qdrant/models.js.map +1 -0
  46. package/dist/esm/compat/qdrant.d.ts +4 -0
  47. package/dist/esm/compat/qdrant.d.ts.map +1 -0
  48. package/dist/esm/compat/qdrant.js +4 -0
  49. package/dist/esm/compat/qdrant.js.map +1 -0
  50. package/jsr.json +3 -1
  51. package/package.json +32 -1
  52. package/src/compat/qdrant/client.ts +901 -0
  53. package/src/compat/qdrant/conversions.ts +422 -0
  54. package/src/compat/qdrant/errors.ts +20 -0
  55. package/src/compat/qdrant/filters.ts +213 -0
  56. package/src/compat/qdrant/models.ts +317 -0
  57. package/src/compat/qdrant.ts +3 -0
  58. package/test/integration/qdrant-compat-external.test.mjs +226 -0
  59. package/test/integration/qdrant-compat-live.test.mjs +122 -0
  60. package/test/qdrant-compat.test.mjs +402 -0
@@ -0,0 +1,317 @@
1
+ type JsonObject = { [key: string]: unknown };
2
+
3
+ export const Distance = {
4
+ COSINE: "Cosine",
5
+ EUCLID: "Euclid",
6
+ DOT: "Dot",
7
+ MANHATTAN: "Manhattan",
8
+ Cosine: "Cosine",
9
+ Euclid: "Euclid",
10
+ Dot: "Dot",
11
+ Manhattan: "Manhattan",
12
+ } as const;
13
+ export type Distance = (typeof Distance)[keyof typeof Distance];
14
+
15
+ export const PayloadSchemaType = {
16
+ KEYWORD: "keyword",
17
+ INTEGER: "integer",
18
+ FLOAT: "float",
19
+ BOOL: "bool",
20
+ DATETIME: "datetime",
21
+ TEXT: "text",
22
+ UUID: "uuid",
23
+ GEO: "geo",
24
+ Keyword: "keyword",
25
+ Integer: "integer",
26
+ Float: "float",
27
+ Bool: "bool",
28
+ Datetime: "datetime",
29
+ Text: "text",
30
+ Uuid: "uuid",
31
+ Geo: "geo",
32
+ } as const;
33
+ export type PayloadSchemaType =
34
+ (typeof PayloadSchemaType)[keyof typeof PayloadSchemaType];
35
+
36
+ export const UpdateStatus = {
37
+ ACKNOWLEDGED: "acknowledged",
38
+ COMPLETED: "completed",
39
+ } as const;
40
+ export type UpdateStatus = (typeof UpdateStatus)[keyof typeof UpdateStatus];
41
+
42
+ export type PointId = string | number;
43
+ export type DenseVector = number[];
44
+ export type NamedVector = {
45
+ [name: string]: DenseVector | SparseVector | JsonObject;
46
+ };
47
+ export type PointVector = DenseVector | NamedVector;
48
+
49
+ function assignExtras<T extends object>(target: T, init: JsonObject): T {
50
+ for (const [key, value] of Object.entries(init)) {
51
+ if (!(key in target)) {
52
+ Object.assign(target, { [key]: value });
53
+ }
54
+ }
55
+ return target;
56
+ }
57
+
58
+ export class VectorParams {
59
+ size: number;
60
+ distance: Distance;
61
+
62
+ constructor(init: { size: number; distance?: Distance | string | undefined }) {
63
+ this.size = init.size;
64
+ this.distance = normalizeDistance(init.distance);
65
+ assignExtras(this, init as JsonObject);
66
+ }
67
+ }
68
+
69
+ export class SparseVector {
70
+ indices: number[];
71
+ values: number[];
72
+
73
+ constructor(init: { indices: number[]; values: number[] }) {
74
+ this.indices = init.indices;
75
+ this.values = init.values;
76
+ assignExtras(this, init as JsonObject);
77
+ }
78
+ }
79
+
80
+ export class PointStruct {
81
+ id: PointId;
82
+ vector: PointVector;
83
+ payload: JsonObject | undefined;
84
+
85
+ constructor(init: {
86
+ id: PointId;
87
+ vector: PointVector;
88
+ payload?: JsonObject | null | undefined;
89
+ }) {
90
+ this.id = init.id;
91
+ this.vector = init.vector;
92
+ this.payload = init.payload ?? undefined;
93
+ assignExtras(this, init as JsonObject);
94
+ }
95
+ }
96
+
97
+ export class MatchValue {
98
+ value: unknown;
99
+
100
+ constructor(init: { value: unknown }) {
101
+ this.value = init.value;
102
+ assignExtras(this, init as JsonObject);
103
+ }
104
+ }
105
+
106
+ export class MatchAny {
107
+ any: unknown[];
108
+
109
+ constructor(init: { any: unknown[] }) {
110
+ this.any = init.any;
111
+ assignExtras(this, init as JsonObject);
112
+ }
113
+ }
114
+
115
+ export class MatchExcept {
116
+ except: unknown[];
117
+ except_: unknown[];
118
+
119
+ constructor(init: { except?: unknown[] | undefined; except_?: unknown[] | undefined }) {
120
+ this.except = init.except ?? init.except_ ?? [];
121
+ this.except_ = this.except;
122
+ assignExtras(this, init as JsonObject);
123
+ }
124
+ }
125
+
126
+ export class MatchText {
127
+ text: string;
128
+
129
+ constructor(init: { text: string }) {
130
+ this.text = init.text;
131
+ assignExtras(this, init as JsonObject);
132
+ }
133
+ }
134
+
135
+ export class Range {
136
+ gt: unknown;
137
+ gte: unknown;
138
+ lt: unknown;
139
+ lte: unknown;
140
+
141
+ constructor(init: {
142
+ gt?: unknown;
143
+ gte?: unknown;
144
+ lt?: unknown;
145
+ lte?: unknown;
146
+ }) {
147
+ this.gt = init.gt;
148
+ this.gte = init.gte;
149
+ this.lt = init.lt;
150
+ this.lte = init.lte;
151
+ assignExtras(this, init as JsonObject);
152
+ }
153
+ }
154
+
155
+ export type MatchCondition =
156
+ | MatchValue
157
+ | MatchAny
158
+ | MatchExcept
159
+ | MatchText
160
+ | JsonObject;
161
+
162
+ export class FieldCondition {
163
+ key: string;
164
+ match: MatchCondition | undefined;
165
+ range: Range | JsonObject | undefined;
166
+
167
+ constructor(init: {
168
+ key: string;
169
+ match?: MatchCondition | null | undefined;
170
+ range?: Range | JsonObject | null | undefined;
171
+ }) {
172
+ this.key = init.key;
173
+ this.match = init.match ?? undefined;
174
+ this.range = init.range ?? undefined;
175
+ assignExtras(this, init as JsonObject);
176
+ }
177
+ }
178
+
179
+ export class HasIdCondition {
180
+ hasId: PointId[];
181
+ has_id: PointId[];
182
+
183
+ constructor(init: { hasId?: PointId[] | undefined; has_id?: PointId[] | undefined }) {
184
+ this.hasId = init.hasId ?? init.has_id ?? [];
185
+ this.has_id = this.hasId;
186
+ assignExtras(this, init as JsonObject);
187
+ }
188
+ }
189
+
190
+ export type Condition = FieldCondition | HasIdCondition | JsonObject;
191
+
192
+ export class Filter {
193
+ must: Condition[] | undefined;
194
+ should: Condition[] | undefined;
195
+ mustNot: Condition[] | undefined;
196
+ must_not: Condition[] | undefined;
197
+
198
+ constructor(init: {
199
+ must?: Condition[] | null | undefined;
200
+ should?: Condition[] | null | undefined;
201
+ mustNot?: Condition[] | null | undefined;
202
+ must_not?: Condition[] | null | undefined;
203
+ } = {}) {
204
+ this.must = init.must ?? undefined;
205
+ this.should = init.should ?? undefined;
206
+ this.mustNot = init.mustNot ?? init.must_not ?? undefined;
207
+ this.must_not = this.mustNot;
208
+ assignExtras(this, init as JsonObject);
209
+ }
210
+ }
211
+
212
+ export class SearchParams {
213
+ hnswEf: number | undefined;
214
+ hnsw_ef: number | undefined;
215
+ exact: boolean | undefined;
216
+
217
+ constructor(init: {
218
+ hnswEf?: number | undefined;
219
+ hnsw_ef?: number | undefined;
220
+ exact?: boolean | undefined;
221
+ } = {}) {
222
+ this.hnswEf = init.hnswEf ?? init.hnsw_ef;
223
+ this.hnsw_ef = this.hnswEf;
224
+ this.exact = init.exact;
225
+ assignExtras(this, init as JsonObject);
226
+ }
227
+ }
228
+
229
+ export class ScoredPoint {
230
+ id: PointId;
231
+ version: number | undefined;
232
+ score: number | undefined;
233
+ payload: JsonObject | undefined;
234
+ vector: unknown;
235
+
236
+ constructor(init: {
237
+ id: PointId;
238
+ version?: number | undefined;
239
+ score?: number | undefined;
240
+ payload?: JsonObject | null | undefined;
241
+ vector?: unknown;
242
+ }) {
243
+ this.id = init.id;
244
+ this.version = init.version;
245
+ this.score = init.score;
246
+ this.payload = init.payload ?? undefined;
247
+ this.vector = init.vector;
248
+ assignExtras(this, init as JsonObject);
249
+ }
250
+ }
251
+
252
+ export class Record {
253
+ id: PointId;
254
+ payload: JsonObject | undefined;
255
+ vector: unknown;
256
+
257
+ constructor(init: {
258
+ id: PointId;
259
+ payload?: JsonObject | null | undefined;
260
+ vector?: unknown;
261
+ }) {
262
+ this.id = init.id;
263
+ this.payload = init.payload ?? undefined;
264
+ this.vector = init.vector;
265
+ assignExtras(this, init as JsonObject);
266
+ }
267
+ }
268
+
269
+ export class QueryResponse {
270
+ points: ScoredPoint[];
271
+
272
+ constructor(init: { points: ScoredPoint[] }) {
273
+ this.points = init.points;
274
+ assignExtras(this, init as JsonObject);
275
+ }
276
+ }
277
+
278
+ export class UpdateResult {
279
+ operationId: PointId | undefined;
280
+ operation_id: PointId | undefined;
281
+ status: UpdateStatus;
282
+
283
+ constructor(init: {
284
+ operationId?: PointId | undefined;
285
+ operation_id?: PointId | undefined;
286
+ status?: UpdateStatus | string | undefined;
287
+ } = {}) {
288
+ this.operationId = init.operationId ?? init.operation_id;
289
+ this.operation_id = this.operationId;
290
+ this.status = normalizeUpdateStatus(init.status);
291
+ assignExtras(this, init as JsonObject);
292
+ }
293
+ }
294
+
295
+ export class CountResult {
296
+ count: number;
297
+
298
+ constructor(init: { count: number }) {
299
+ this.count = init.count;
300
+ assignExtras(this, init as JsonObject);
301
+ }
302
+ }
303
+
304
+ function normalizeDistance(distance: Distance | string | undefined): Distance {
305
+ const value = distance ?? Distance.COSINE;
306
+ if (value === "Cosine" || value === "cosine") return Distance.COSINE;
307
+ if (value === "Euclid" || value === "euclidean") return Distance.EUCLID;
308
+ if (value === "Dot" || value === "dot_product") return Distance.DOT;
309
+ if (value === "Manhattan" || value === "manhattan") return Distance.MANHATTAN;
310
+ return value as Distance;
311
+ }
312
+
313
+ function normalizeUpdateStatus(status: UpdateStatus | string | undefined): UpdateStatus {
314
+ const value = status ?? UpdateStatus.COMPLETED;
315
+ if (value === UpdateStatus.ACKNOWLEDGED) return UpdateStatus.ACKNOWLEDGED;
316
+ return UpdateStatus.COMPLETED;
317
+ }
@@ -0,0 +1,3 @@
1
+ export { QdrantCompatClient, QdrantClient } from "./qdrant/client.js";
2
+ export * as models from "./qdrant/models.js";
3
+ export * from "./qdrant/errors.js";
@@ -0,0 +1,226 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { Document as LangChainDocument } from "@langchain/core/documents";
5
+ import { QdrantVectorStore as LangChainQdrantVectorStore } from "@langchain/qdrant";
6
+ import { Settings } from "@llamaindex/core/global";
7
+ import { TextNode } from "@llamaindex/core/schema";
8
+ import { QdrantVectorStore as LlamaIndexQdrantVectorStore } from "@llamaindex/qdrant";
9
+
10
+ import { QdrantCompatClient } from "../../dist/esm/compat/qdrant.js";
11
+
12
+ const shouldRun = process.env.LAMBDADB_RUN_EXTERNAL_INTEGRATION_TESTS === "1";
13
+
14
+ class StaticEmbeddings {
15
+ async embedDocuments(texts) {
16
+ return texts.map(vectorForText);
17
+ }
18
+
19
+ async embedQuery(text) {
20
+ return vectorForText(text);
21
+ }
22
+ }
23
+
24
+ class InMemoryDocs {
25
+ constructor(collection) {
26
+ this.collection = collection;
27
+ }
28
+
29
+ async upsert(body) {
30
+ for (const doc of body.docs) {
31
+ this.collection.docs.set(String(doc.id), doc);
32
+ }
33
+ return { message: "ok" };
34
+ }
35
+
36
+ async fetch(body) {
37
+ return {
38
+ docs: body.ids
39
+ .map((id) => this.collection.docs.get(String(id)))
40
+ .filter(Boolean)
41
+ .map((doc) => ({ collection: this.collection.name, doc })),
42
+ total: body.ids.length,
43
+ took: 0,
44
+ isDocsInline: true,
45
+ };
46
+ }
47
+
48
+ async delete(body) {
49
+ for (const id of body.ids) {
50
+ this.collection.docs.delete(String(id));
51
+ }
52
+ return { message: "ok" };
53
+ }
54
+
55
+ async list(body = {}) {
56
+ return {
57
+ docs: Array.from(this.collection.docs.values())
58
+ .slice(0, body.size ?? 10)
59
+ .map((doc) => ({ collection: this.collection.name, doc })),
60
+ isDocsInline: true,
61
+ total: this.collection.docs.size,
62
+ };
63
+ }
64
+ }
65
+
66
+ class InMemoryCollection {
67
+ constructor(owner, name) {
68
+ this.owner = owner;
69
+ this.name = name;
70
+ this.docs = new Map();
71
+ this.docApi = new InMemoryDocs(this);
72
+ this.indexConfigs = {};
73
+ }
74
+
75
+ handle() {
76
+ return {
77
+ docs: this.docApi,
78
+ get: async () => ({
79
+ collection: {
80
+ collectionName: this.name,
81
+ indexConfigs: this.indexConfigs,
82
+ numDocs: this.docs.size,
83
+ collectionStatus: "ACTIVE",
84
+ },
85
+ }),
86
+ update: async (body) => {
87
+ this.indexConfigs = body.indexConfigs;
88
+ return { collection: { collectionName: this.name, indexConfigs: this.indexConfigs } };
89
+ },
90
+ delete: async () => {
91
+ this.owner.collections.delete(this.name);
92
+ return { message: "ok" };
93
+ },
94
+ query: async (body) => {
95
+ const knn = body.query.knn;
96
+ const queryVector = knn.queryVector;
97
+ const docs = Array.from(this.docs.values())
98
+ .map((doc) => ({
99
+ collection: this.name,
100
+ doc,
101
+ score: dot(queryVector, doc[knn.field]),
102
+ }))
103
+ .sort((a, b) => b.score - a.score)
104
+ .slice(0, body.size ?? knn.k ?? 10);
105
+ return {
106
+ docs,
107
+ total: docs.length,
108
+ took: 0,
109
+ isDocsInline: true,
110
+ };
111
+ },
112
+ };
113
+ }
114
+ }
115
+
116
+ class InMemoryLambdaDB {
117
+ constructor() {
118
+ this.collections = new Map();
119
+ }
120
+
121
+ async listCollections() {
122
+ return {
123
+ collections: Array.from(this.collections.keys()).map((collectionName) => ({
124
+ collectionName,
125
+ })),
126
+ };
127
+ }
128
+
129
+ async createCollection(body) {
130
+ const collection = this.ensureCollection(body.collectionName);
131
+ collection.indexConfigs = body.indexConfigs ?? {};
132
+ return { collection: { collectionName: body.collectionName } };
133
+ }
134
+
135
+ collection(name) {
136
+ return this.ensureCollection(name).handle();
137
+ }
138
+
139
+ ensureCollection(name) {
140
+ if (!this.collections.has(name)) {
141
+ this.collections.set(name, new InMemoryCollection(this, name));
142
+ }
143
+ return this.collections.get(name);
144
+ }
145
+ }
146
+
147
+ test("langchain qdrant vector store works with QdrantCompatClient", {
148
+ skip: shouldRun ? false : "Set LAMBDADB_RUN_EXTERNAL_INTEGRATION_TESTS=1",
149
+ }, async () => {
150
+ Settings.embedModel = {
151
+ getTextEmbedding: async (text) => vectorForText(text),
152
+ getQueryEmbedding: async (text) => vectorForText(text),
153
+ };
154
+ const client = new QdrantCompatClient(new InMemoryLambdaDB());
155
+ const embeddings = new StaticEmbeddings();
156
+
157
+ const store = await LangChainQdrantVectorStore.fromDocuments(
158
+ [
159
+ new LangChainDocument({
160
+ pageContent: "alpha",
161
+ metadata: { tenant: "acme" },
162
+ }),
163
+ new LangChainDocument({
164
+ pageContent: "beta",
165
+ metadata: { tenant: "acme" },
166
+ }),
167
+ ],
168
+ embeddings,
169
+ {
170
+ client,
171
+ collectionName: "langchain-docs",
172
+ },
173
+ );
174
+
175
+ const results = await store.similaritySearchVectorWithScore([1, 0, 0], 1);
176
+
177
+ assert.equal(results.length, 1);
178
+ assert.equal(results[0][0].pageContent, "alpha");
179
+ assert.equal(results[0][0].metadata.tenant, "acme");
180
+ });
181
+
182
+ test("llamaindex qdrant vector store works with QdrantCompatClient", {
183
+ skip: shouldRun ? false : "Set LAMBDADB_RUN_EXTERNAL_INTEGRATION_TESTS=1",
184
+ }, async () => {
185
+ const client = new QdrantCompatClient(new InMemoryLambdaDB());
186
+ const store = new LlamaIndexQdrantVectorStore({
187
+ client,
188
+ collectionName: "llamaindex-docs",
189
+ });
190
+
191
+ const alpha = new TextNode({
192
+ text: "alpha",
193
+ metadata: { tenant: "acme" },
194
+ });
195
+ alpha.id_ = "alpha";
196
+ alpha.embedding = [1, 0, 0];
197
+
198
+ const beta = new TextNode({
199
+ text: "beta",
200
+ metadata: { tenant: "acme" },
201
+ });
202
+ beta.id_ = "beta";
203
+ beta.embedding = [0, 1, 0];
204
+
205
+ const ids = await store.add([alpha, beta]);
206
+ assert.deepEqual(ids, ["alpha", "beta"]);
207
+
208
+ const results = await store.query({
209
+ queryEmbedding: [1, 0, 0],
210
+ similarityTopK: 1,
211
+ });
212
+
213
+ assert.equal(results.ids[0], "alpha");
214
+ assert.equal(results.nodes[0].getContent(), "alpha");
215
+ });
216
+
217
+ function vectorForText(text) {
218
+ if (text.includes("alpha")) return [1, 0, 0];
219
+ if (text.includes("beta")) return [0, 1, 0];
220
+ return [0, 0, 1];
221
+ }
222
+
223
+ function dot(left, right) {
224
+ if (!Array.isArray(left) || !Array.isArray(right)) return 0;
225
+ return left.reduce((sum, value, index) => sum + value * (right[index] ?? 0), 0);
226
+ }
@@ -0,0 +1,122 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ QdrantCompatClient,
6
+ UnsupportedQdrantFeatureError,
7
+ models,
8
+ } from "../../dist/esm/compat/qdrant.js";
9
+
10
+ const shouldRun = process.env.LAMBDADB_RUN_LIVE_TESTS === "1";
11
+ const projectApiKey = process.env.LAMBDADB_PROJECT_API_KEY;
12
+ const projectName = process.env.LAMBDADB_PROJECT_NAME || "playground";
13
+ const baseUrl = process.env.LAMBDADB_BASE_URL || "https://api.lambdadb.ai";
14
+
15
+ test("qdrant compatibility live smoke", {
16
+ skip: shouldRun && projectApiKey
17
+ ? false
18
+ : "Set LAMBDADB_RUN_LIVE_TESTS=1 and LAMBDADB_PROJECT_API_KEY to run live tests",
19
+ }, async () => {
20
+ const client = new QdrantCompatClient({
21
+ projectApiKey,
22
+ projectName,
23
+ baseUrl,
24
+ });
25
+ const collectionName = `qdrant-live-${Date.now()}`;
26
+
27
+ try {
28
+ await client.createCollection(collectionName, {
29
+ vectorsConfig: new models.VectorParams({
30
+ size: 3,
31
+ distance: models.Distance.COSINE,
32
+ }),
33
+ payloadSchema: {
34
+ tenant: models.PayloadSchemaType.KEYWORD,
35
+ },
36
+ });
37
+
38
+ assert.equal(await client.collectionExists(collectionName), true);
39
+
40
+ const upsertResult = await client.upsert(collectionName, {
41
+ points: [
42
+ new models.PointStruct({
43
+ id: 1,
44
+ vector: [1.0, 0.0, 0.0],
45
+ payload: { tenant: "acme", title: "alpha" },
46
+ }),
47
+ new models.PointStruct({
48
+ id: 2,
49
+ vector: [0.0, 1.0, 0.0],
50
+ payload: { tenant: "acme", title: "beta" },
51
+ }),
52
+ new models.PointStruct({
53
+ id: 3,
54
+ vector: [0.0, 0.0, 1.0],
55
+ payload: { tenant: "other", title: "gamma" },
56
+ }),
57
+ ],
58
+ });
59
+ assert.equal(upsertResult.status, models.UpdateStatus.COMPLETED);
60
+
61
+ const queryResult = await client.queryPoints(collectionName, {
62
+ query: [1.0, 0.0, 0.0],
63
+ queryFilter: new models.Filter({
64
+ must: [
65
+ new models.FieldCondition({
66
+ key: "tenant",
67
+ match: new models.MatchValue({ value: "acme" }),
68
+ }),
69
+ ],
70
+ }),
71
+ limit: 2,
72
+ });
73
+ assert.ok(queryResult.points.length > 0);
74
+ assert.equal(queryResult.points[0].payload?.tenant, "acme");
75
+
76
+ const records = await client.retrieve(collectionName, { ids: [1] });
77
+ assert.equal(records[0]?.id, 1);
78
+ assert.equal(records[0]?.payload?.tenant, "acme");
79
+
80
+ const deleteResult = await client.delete(collectionName, {
81
+ pointsSelector: [1],
82
+ });
83
+ assert.equal(deleteResult.status, models.UpdateStatus.COMPLETED);
84
+
85
+ const deleteByFilterResult = await client.delete(collectionName, {
86
+ pointsSelector: {
87
+ filter: new models.Filter({
88
+ must: [
89
+ new models.FieldCondition({
90
+ key: "tenant",
91
+ match: new models.MatchValue({ value: "other" }),
92
+ }),
93
+ ],
94
+ }),
95
+ },
96
+ });
97
+ assert.equal(deleteByFilterResult.status, models.UpdateStatus.COMPLETED);
98
+
99
+ const deletedByFilterRecords = await client.retrieve(collectionName, { ids: [3] });
100
+ assert.equal(deletedByFilterRecords.length, 0);
101
+
102
+ await assert.rejects(
103
+ client.scroll(collectionName, {
104
+ scrollFilter: new models.Filter({
105
+ must: [
106
+ new models.FieldCondition({
107
+ key: "tenant",
108
+ match: new models.MatchValue({ value: "acme" }),
109
+ }),
110
+ ],
111
+ }),
112
+ }),
113
+ UnsupportedQdrantFeatureError,
114
+ );
115
+ } finally {
116
+ try {
117
+ await client.deleteCollection(collectionName);
118
+ } catch (error) {
119
+ if (error?.name !== "ResourceNotFoundError") throw error;
120
+ }
121
+ }
122
+ });