@functional-systems/lambdadb 0.4.0 → 0.4.1

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.
@@ -2,6 +2,7 @@ import { LambdaDBClient, type LambdaDBClientOptions } from "../../client.js";
2
2
  import { ResourceNotFoundError } from "../../models/errors/index.js";
3
3
  import type { CreateCollectionInput } from "../../types/public.js";
4
4
  import {
5
+ ORIGINAL_ID_FIELD,
5
6
  mergeIndexConfigs,
6
7
  payloadSchemaToIndexConfigs,
7
8
  plainIndexConfigs,
@@ -10,6 +11,7 @@ import {
10
11
  queryVectorAndField,
11
12
  resultToScoredPoint,
12
13
  docToRecord,
14
+ vectorFieldName,
13
15
  vectorConfigToIndexConfigs,
14
16
  type IndexConfigs,
15
17
  } from "./conversions.js";
@@ -82,10 +84,10 @@ type QueryPointsOptions = {
82
84
  queryFilter?: models.Filter | JsonObject | null;
83
85
  query_filter?: models.Filter | JsonObject | null;
84
86
  limit?: number | undefined;
85
- withPayload?: boolean | undefined;
86
- with_payload?: boolean | undefined;
87
- withVectors?: boolean | undefined;
88
- with_vectors?: boolean | undefined;
87
+ withPayload?: PayloadSelector | undefined;
88
+ with_payload?: PayloadSelector | undefined;
89
+ withVectors?: VectorSelector | undefined;
90
+ with_vectors?: VectorSelector | undefined;
89
91
  using?: string | undefined;
90
92
  searchParams?: models.SearchParams | JsonObject | null;
91
93
  search_params?: models.SearchParams | JsonObject | null;
@@ -98,6 +100,9 @@ type QueryPointsOptions = {
98
100
  [key: string]: unknown;
99
101
  };
100
102
 
103
+ type PayloadSelector = boolean | string[];
104
+ type VectorSelector = boolean | string[];
105
+
101
106
  export class QdrantCompatClient {
102
107
  private readonly client: LambdaDBLike;
103
108
 
@@ -399,10 +404,10 @@ export class QdrantCompatClient {
399
404
  collectionName: string,
400
405
  options: {
401
406
  ids: models.PointId[];
402
- withPayload?: boolean | undefined;
403
- with_payload?: boolean | undefined;
404
- withVectors?: boolean | undefined;
405
- with_vectors?: boolean | undefined;
407
+ withPayload?: PayloadSelector | undefined;
408
+ with_payload?: PayloadSelector | undefined;
409
+ withVectors?: VectorSelector | undefined;
410
+ with_vectors?: VectorSelector | undefined;
406
411
  [key: string]: unknown;
407
412
  },
408
413
  ): Promise<models.Record[]> {
@@ -415,15 +420,19 @@ export class QdrantCompatClient {
415
420
  ...rest
416
421
  } = options;
417
422
  warnIgnored(rest);
418
- const includeVectors = withVectors ?? withVectorsSnake ?? false;
419
- const response = await this.client.collection(collectionName).docs.fetch({
423
+ const payloadSelector = normalizePayloadSelector(withPayload ?? withPayloadSnake ?? true);
424
+ const vectorSelector = normalizeVectorSelector(withVectors ?? withVectorsSnake ?? false);
425
+ const fetchBody: JsonObject = {
420
426
  ids: ids.map(String),
421
427
  consistentRead: true,
422
- includeVectors,
423
- });
428
+ includeVectors: vectorSelector !== false,
429
+ };
430
+ const fields = fieldsForSelectors(payloadSelector, vectorSelector);
431
+ if (fields !== undefined) fetchBody["fields"] = fields;
432
+ const response = await this.client.collection(collectionName).docs.fetch(fetchBody);
424
433
  return response.docs.map((doc) => docToRecord(doc, {
425
- withPayload: withPayload ?? withPayloadSnake ?? true,
426
- withVectors: includeVectors,
434
+ withPayload: payloadSelector,
435
+ withVectors: vectorSelector,
427
436
  }));
428
437
  }
429
438
 
@@ -512,7 +521,8 @@ export class QdrantCompatClient {
512
521
  warn("Qdrant searchParams are ignored by the LambdaDB compatibility client");
513
522
  }
514
523
 
515
- const includeVectors = withVectors ?? withVectorsSnake ?? false;
524
+ const payloadSelector = normalizePayloadSelector(withPayload ?? withPayloadSnake ?? true);
525
+ const vectorSelector = normalizeVectorSelector(withVectors ?? withVectorsSnake ?? false);
516
526
  const { vector, field } = queryVectorAndField(query, using);
517
527
  const knn: JsonObject = {
518
528
  field,
@@ -522,16 +532,19 @@ export class QdrantCompatClient {
522
532
  const convertedFilter = filterToLambdaDB(queryFilter ?? queryFilterSnake);
523
533
  if (Object.keys(convertedFilter).length > 0) knn["filter"] = convertedFilter;
524
534
 
525
- const response = await this.client.collection(collectionName).query({
535
+ const queryBody: JsonObject = {
526
536
  query: { knn },
527
537
  size: limit,
528
538
  consistentRead: true,
529
- includeVectors,
530
- });
539
+ includeVectors: vectorSelector !== false,
540
+ };
541
+ const fields = fieldsForSelectors(payloadSelector, vectorSelector);
542
+ if (fields !== undefined) queryBody["fields"] = fields;
543
+ const response = await this.client.collection(collectionName).query(queryBody);
531
544
  return new models.QueryResponse({
532
545
  points: response.docs.map((result) => resultToScoredPoint(result, {
533
- withPayload: withPayload ?? withPayloadSnake ?? true,
534
- withVectors: includeVectors,
546
+ withPayload: payloadSelector,
547
+ withVectors: vectorSelector,
535
548
  })),
536
549
  });
537
550
  }
@@ -549,10 +562,10 @@ export class QdrantCompatClient {
549
562
  query: unknown;
550
563
  limit?: number | undefined;
551
564
  filter?: models.Filter | JsonObject | null;
552
- with_payload?: boolean | unknown[] | undefined;
553
- withPayload?: boolean | unknown[] | undefined;
554
- with_vector?: boolean | undefined;
555
- withVectors?: boolean | undefined;
565
+ with_payload?: PayloadSelector | undefined;
566
+ withPayload?: PayloadSelector | undefined;
567
+ with_vector?: VectorSelector | undefined;
568
+ withVectors?: VectorSelector | undefined;
556
569
  params?: unknown;
557
570
  [key: string]: unknown;
558
571
  },
@@ -574,9 +587,9 @@ export class QdrantCompatClient {
574
587
  };
575
588
  if (filter !== undefined) queryOptions.queryFilter = filter;
576
589
  if (limit !== undefined) queryOptions.limit = limit;
577
- const payloadSelector = payloadSelectorToBoolean(withPayload ?? withPayloadSnake);
590
+ const payloadSelector = normalizeOptionalPayloadSelector(withPayload ?? withPayloadSnake);
578
591
  if (payloadSelector !== undefined) queryOptions.withPayload = payloadSelector;
579
- const vectorSelector = withVectors ?? withVectorSnake;
592
+ const vectorSelector = normalizeOptionalVectorSelector(withVectors ?? withVectorSnake);
580
593
  if (vectorSelector !== undefined) queryOptions.withVectors = vectorSelector;
581
594
  if (isObject(params)) queryOptions.searchParams = params;
582
595
  return this.queryPoints(collectionName, queryOptions);
@@ -603,10 +616,10 @@ export class QdrantCompatClient {
603
616
  scrollFilter?: models.Filter | JsonObject | null;
604
617
  scroll_filter?: models.Filter | JsonObject | null;
605
618
  limit?: number | undefined;
606
- withPayload?: boolean | undefined;
607
- with_payload?: boolean | undefined;
608
- withVectors?: boolean | undefined;
609
- with_vectors?: boolean | undefined;
619
+ withPayload?: PayloadSelector | undefined;
620
+ with_payload?: PayloadSelector | undefined;
621
+ withVectors?: VectorSelector | undefined;
622
+ with_vectors?: VectorSelector | undefined;
610
623
  [key: string]: unknown;
611
624
  } = {},
612
625
  ): Promise<[models.Record[], string | undefined]> {
@@ -624,13 +637,15 @@ export class QdrantCompatClient {
624
637
  if (scrollFilter !== undefined || scrollFilterSnake !== undefined) {
625
638
  throw new UnsupportedQdrantFeatureError("Filtered scroll is not supported in v1");
626
639
  }
627
- if (withVectors === true || withVectorsSnake === true) {
640
+ const payloadSelector = normalizePayloadSelector(withPayload ?? withPayloadSnake ?? true);
641
+ const vectorSelector = normalizeVectorSelector(withVectors ?? withVectorsSnake ?? false);
642
+ if (vectorSelector !== false) {
628
643
  throw new UnsupportedQdrantFeatureError("Scroll with vectors is not supported in v1");
629
644
  }
630
645
  const response = await this.client.collection(collectionName).docs.list({ size: limit });
631
646
  return [
632
647
  response.docs.map((doc) => docToRecord(doc, {
633
- withPayload: withPayload ?? withPayloadSnake ?? true,
648
+ withPayload: payloadSelector,
634
649
  withVectors: false,
635
650
  })),
636
651
  response.nextPageToken,
@@ -851,9 +866,48 @@ function hasSameIndexConfigs(existing: IndexConfigs, expected: IndexConfigs): bo
851
866
  );
852
867
  }
853
868
 
854
- function payloadSelectorToBoolean(selector: boolean | unknown[] | undefined): boolean | undefined {
855
- if (Array.isArray(selector)) return true;
856
- return selector;
869
+ function normalizeOptionalPayloadSelector(selector: unknown): PayloadSelector | undefined {
870
+ if (selector === undefined) return undefined;
871
+ return normalizePayloadSelector(selector);
872
+ }
873
+
874
+ function normalizePayloadSelector(selector: unknown): PayloadSelector {
875
+ if (selector === true || selector === false) return selector;
876
+ if (Array.isArray(selector) && selector.every((item) => typeof item === "string")) {
877
+ return selector;
878
+ }
879
+ throw new QdrantCompatValidationError(
880
+ "withPayload/with_payload must be a boolean or a string field list",
881
+ );
882
+ }
883
+
884
+ function normalizeOptionalVectorSelector(selector: unknown): VectorSelector | undefined {
885
+ if (selector === undefined) return undefined;
886
+ return normalizeVectorSelector(selector);
887
+ }
888
+
889
+ function normalizeVectorSelector(selector: unknown): VectorSelector {
890
+ if (selector === true || selector === false) return selector;
891
+ if (Array.isArray(selector) && selector.every((item) => typeof item === "string")) {
892
+ return selector;
893
+ }
894
+ throw new QdrantCompatValidationError(
895
+ "withVectors/with_vector must be a boolean or a string vector-name list",
896
+ );
897
+ }
898
+
899
+ function fieldsForSelectors(
900
+ payloadSelector: PayloadSelector,
901
+ vectorSelector: VectorSelector,
902
+ ): { include: string[] } | undefined {
903
+ if (!Array.isArray(payloadSelector)) return undefined;
904
+ if (vectorSelector === true) return undefined;
905
+
906
+ const include = new Set<string>([ORIGINAL_ID_FIELD, ...payloadSelector]);
907
+ if (Array.isArray(vectorSelector)) {
908
+ for (const name of vectorSelector) include.add(vectorFieldName(name));
909
+ }
910
+ return { include: Array.from(include) };
857
911
  }
858
912
 
859
913
  function isLambdaDBLike(value: unknown): value is LambdaDBLike {
@@ -96,19 +96,19 @@ export function pointToDoc(point: unknown): JsonObject {
96
96
 
97
97
  export function docToRecord(
98
98
  input: unknown,
99
- options: { withPayload?: boolean; withVectors?: boolean } = {},
99
+ options: { withPayload?: boolean | string[]; withVectors?: boolean | string[] } = {},
100
100
  ): models.Record {
101
101
  const doc = unwrapDoc(input);
102
102
  return new models.Record({
103
103
  id: docId(doc),
104
- payload: options.withPayload === false ? undefined : payloadFromDoc(doc),
105
- vector: vectorFromDoc(doc, options.withVectors === true),
104
+ payload: payloadFromDoc(doc, options.withPayload ?? true),
105
+ vector: vectorFromDoc(doc, options.withVectors ?? false),
106
106
  });
107
107
  }
108
108
 
109
109
  export function resultToScoredPoint(
110
110
  result: unknown,
111
- options: { withPayload?: boolean; withVectors?: boolean } = {},
111
+ options: { withPayload?: boolean | string[]; withVectors?: boolean | string[] } = {},
112
112
  ): models.ScoredPoint {
113
113
  const doc = unwrapDoc(result);
114
114
  const score = isObject(result) && typeof result["score"] === "number"
@@ -117,8 +117,8 @@ export function resultToScoredPoint(
117
117
  return new models.ScoredPoint({
118
118
  id: docId(doc),
119
119
  score,
120
- payload: options.withPayload === false ? undefined : payloadFromDoc(doc),
121
- vector: vectorFromDoc(doc, options.withVectors === true),
120
+ payload: payloadFromDoc(doc, options.withPayload ?? true),
121
+ vector: vectorFromDoc(doc, options.withVectors ?? false),
122
122
  });
123
123
  }
124
124
 
@@ -356,26 +356,34 @@ function validatePayload(payload: JsonObject): void {
356
356
  }
357
357
  }
358
358
 
359
- function payloadFromDoc(doc: JsonObject): JsonObject {
359
+ function payloadFromDoc(doc: JsonObject, selector: boolean | string[]): JsonObject | undefined {
360
+ if (selector === false) return undefined;
361
+ const selectedFields = Array.isArray(selector) ? new Set(selector) : undefined;
360
362
  const payload: JsonObject = {};
361
363
  for (const [key, value] of Object.entries(doc)) {
362
- if (key !== ID_FIELD && !key.startsWith(RESERVED_PREFIX)) {
364
+ if (
365
+ key !== ID_FIELD &&
366
+ !key.startsWith(RESERVED_PREFIX) &&
367
+ (selectedFields === undefined || selectedFields.has(key))
368
+ ) {
363
369
  payload[key] = value;
364
370
  }
365
371
  }
366
372
  return payload;
367
373
  }
368
374
 
369
- function vectorFromDoc(doc: JsonObject, withVectors: boolean): unknown {
370
- if (!withVectors) return undefined;
375
+ function vectorFromDoc(doc: JsonObject, selector: boolean | string[]): unknown {
376
+ if (selector === false) return undefined;
377
+ const selectedNames = Array.isArray(selector) ? new Set(selector) : undefined;
371
378
 
372
379
  const named: JsonObject = {};
373
380
  let defaultVector: unknown;
374
381
  for (const [key, value] of Object.entries(doc)) {
375
- if (key === DEFAULT_VECTOR_NAME) {
382
+ if (key === DEFAULT_VECTOR_NAME && shouldIncludeVectorName("", key, selectedNames)) {
376
383
  defaultVector = value;
377
384
  } else if (key.startsWith(`${DEFAULT_VECTOR_NAME}_`)) {
378
- named[key.slice(DEFAULT_VECTOR_NAME.length + 1)] = value;
385
+ const name = key.slice(DEFAULT_VECTOR_NAME.length + 1);
386
+ if (shouldIncludeVectorName(name, key, selectedNames)) named[name] = value;
379
387
  }
380
388
  }
381
389
 
@@ -386,6 +394,16 @@ function vectorFromDoc(doc: JsonObject, withVectors: boolean): unknown {
386
394
  return defaultVector;
387
395
  }
388
396
 
397
+ function shouldIncludeVectorName(
398
+ qdrantName: string,
399
+ internalName: string,
400
+ selectedNames: Set<string> | undefined,
401
+ ): boolean {
402
+ return selectedNames === undefined ||
403
+ selectedNames.has(qdrantName) ||
404
+ selectedNames.has(internalName);
405
+ }
406
+
389
407
  function docId(doc: JsonObject): PointId {
390
408
  const originalId = doc[ORIGINAL_ID_FIELD];
391
409
  if (typeof originalId === "string" || typeof originalId === "number") {
@@ -38,7 +38,7 @@ class InMemoryDocs {
38
38
  docs: body.ids
39
39
  .map((id) => this.collection.docs.get(String(id)))
40
40
  .filter(Boolean)
41
- .map((doc) => ({ collection: this.collection.name, doc })),
41
+ .map((doc) => ({ collection: this.collection.name, doc: projectDoc(doc, body.fields) })),
42
42
  total: body.ids.length,
43
43
  took: 0,
44
44
  isDocsInline: true,
@@ -46,8 +46,15 @@ class InMemoryDocs {
46
46
  }
47
47
 
48
48
  async delete(body) {
49
- for (const id of body.ids) {
50
- this.collection.docs.delete(String(id));
49
+ if (body.ids) {
50
+ for (const id of body.ids) {
51
+ this.collection.docs.delete(String(id));
52
+ }
53
+ }
54
+ if (body.filter) {
55
+ for (const [id, doc] of this.collection.docs.entries()) {
56
+ if (matchesLambdaDBFilter(doc, body.filter)) this.collection.docs.delete(id);
57
+ }
51
58
  }
52
59
  return { message: "ok" };
53
60
  }
@@ -56,7 +63,7 @@ class InMemoryDocs {
56
63
  return {
57
64
  docs: Array.from(this.collection.docs.values())
58
65
  .slice(0, body.size ?? 10)
59
- .map((doc) => ({ collection: this.collection.name, doc })),
66
+ .map((doc) => ({ collection: this.collection.name, doc: projectDoc(doc, body.fields) })),
60
67
  isDocsInline: true,
61
68
  total: this.collection.docs.size,
62
69
  };
@@ -95,9 +102,10 @@ class InMemoryCollection {
95
102
  const knn = body.query.knn;
96
103
  const queryVector = knn.queryVector;
97
104
  const docs = Array.from(this.docs.values())
105
+ .filter((doc) => matchesLambdaDBFilter(doc, knn.filter))
98
106
  .map((doc) => ({
99
107
  collection: this.name,
100
- doc,
108
+ doc: projectDoc(doc, body.fields),
101
109
  score: dot(queryVector, doc[knn.field]),
102
110
  }))
103
111
  .sort((a, b) => b.score - a.score)
@@ -179,6 +187,48 @@ test("langchain qdrant vector store works with QdrantCompatClient", {
179
187
  assert.equal(results[0][0].metadata.tenant, "acme");
180
188
  });
181
189
 
190
+ test("langchain qdrant vector store supports filter search and delete", {
191
+ skip: shouldRun ? false : "Set LAMBDADB_RUN_EXTERNAL_INTEGRATION_TESTS=1",
192
+ }, async () => {
193
+ const client = new QdrantCompatClient(new InMemoryLambdaDB());
194
+ const embeddings = new StaticEmbeddings();
195
+
196
+ const store = await LangChainQdrantVectorStore.fromDocuments(
197
+ [
198
+ new LangChainDocument({
199
+ pageContent: "alpha",
200
+ metadata: { tenant: "acme", group: "keep" },
201
+ }),
202
+ new LangChainDocument({
203
+ pageContent: "beta",
204
+ metadata: { tenant: "other", group: "remove" },
205
+ }),
206
+ ],
207
+ embeddings,
208
+ {
209
+ client,
210
+ collectionName: "langchain-filter-docs",
211
+ },
212
+ );
213
+
214
+ const filteredResults = await store.similaritySearchVectorWithScore([1, 0, 0], 2, {
215
+ must: [{ key: "content", match: { value: "alpha" } }],
216
+ });
217
+
218
+ assert.equal(filteredResults.length, 1);
219
+ assert.equal(filteredResults[0][0].pageContent, "alpha");
220
+
221
+ await store.delete({
222
+ filter: {
223
+ must: [{ key: "content", match: { value: "beta" } }],
224
+ },
225
+ });
226
+
227
+ const remainingResults = await store.similaritySearchVectorWithScore([0, 1, 0], 2);
228
+ assert.equal(remainingResults.length, 1);
229
+ assert.equal(remainingResults[0][0].pageContent, "alpha");
230
+ });
231
+
182
232
  test("llamaindex qdrant vector store works with QdrantCompatClient", {
183
233
  skip: shouldRun ? false : "Set LAMBDADB_RUN_EXTERNAL_INTEGRATION_TESTS=1",
184
234
  }, async () => {
@@ -214,6 +264,42 @@ test("llamaindex qdrant vector store works with QdrantCompatClient", {
214
264
  assert.equal(results.nodes[0].getContent(), "alpha");
215
265
  });
216
266
 
267
+ test("llamaindex qdrant vector store supports delete by filter", {
268
+ skip: shouldRun ? false : "Set LAMBDADB_RUN_EXTERNAL_INTEGRATION_TESTS=1",
269
+ }, async () => {
270
+ const client = new QdrantCompatClient(new InMemoryLambdaDB());
271
+ const store = new LlamaIndexQdrantVectorStore({
272
+ client,
273
+ collectionName: "llamaindex-delete-docs",
274
+ });
275
+
276
+ const alpha = new TextNode({
277
+ text: "alpha",
278
+ metadata: { tenant: "acme" },
279
+ relationships: { SOURCE: { nodeId: "alpha-ref" } },
280
+ });
281
+ alpha.id_ = "alpha";
282
+ alpha.embedding = [1, 0, 0];
283
+
284
+ const beta = new TextNode({
285
+ text: "beta",
286
+ metadata: { tenant: "acme" },
287
+ relationships: { SOURCE: { nodeId: "beta-ref" } },
288
+ });
289
+ beta.id_ = "beta";
290
+ beta.embedding = [0, 1, 0];
291
+
292
+ await store.add([alpha, beta]);
293
+ await store.delete("alpha-ref");
294
+
295
+ const results = await store.query({
296
+ queryEmbedding: [1, 0, 0],
297
+ similarityTopK: 2,
298
+ });
299
+
300
+ assert.deepEqual(results.ids, ["beta"]);
301
+ });
302
+
217
303
  function vectorForText(text) {
218
304
  if (text.includes("alpha")) return [1, 0, 0];
219
305
  if (text.includes("beta")) return [0, 1, 0];
@@ -224,3 +310,42 @@ function dot(left, right) {
224
310
  if (!Array.isArray(left) || !Array.isArray(right)) return 0;
225
311
  return left.reduce((sum, value, index) => sum + value * (right[index] ?? 0), 0);
226
312
  }
313
+
314
+ function projectDoc(doc, fields) {
315
+ const include = fields?.include;
316
+ if (!Array.isArray(include)) return doc;
317
+ const projected = { id: doc.id };
318
+ for (const field of include) {
319
+ if (field in doc) projected[field] = doc[field];
320
+ }
321
+ return projected;
322
+ }
323
+
324
+ function matchesLambdaDBFilter(doc, filter) {
325
+ if (!filter || Object.keys(filter).length === 0) return true;
326
+ if (filter.queryString) return matchesQueryString(doc, filter.queryString.query);
327
+ if (Array.isArray(filter.bool)) return matchesBoolClauses(doc, filter.bool);
328
+ if (filter.bool?.clauses) return matchesBoolClauses(doc, filter.bool.clauses);
329
+ return true;
330
+ }
331
+
332
+ function matchesBoolClauses(doc, clauses) {
333
+ const shouldClauses = [];
334
+ for (const clause of clauses) {
335
+ const matched = matchesLambdaDBFilter(doc, clause);
336
+ if (clause.occur === "must_not") {
337
+ if (matched) return false;
338
+ } else if (clause.occur === "should") {
339
+ shouldClauses.push(matched);
340
+ } else if (!matched) {
341
+ return false;
342
+ }
343
+ }
344
+ return shouldClauses.length === 0 || shouldClauses.some(Boolean);
345
+ }
346
+
347
+ function matchesQueryString(doc, query) {
348
+ const [field, ...rest] = String(query).split(":");
349
+ const expected = rest.join(":");
350
+ return String(doc[field]) === expected;
351
+ }
@@ -328,6 +328,59 @@ test("queryPoints maps dense vectors, filters, and responses", async () => {
328
328
  );
329
329
  });
330
330
 
331
+ test("payload and vector selectors map to LambdaDB fields and qdrant responses", async () => {
332
+ const fake = new FakeLambdaDB();
333
+ const collection = fake.collection("docs");
334
+ collection.query = async function query(body) {
335
+ this.queries.push(body);
336
+ return {
337
+ docs: [
338
+ {
339
+ collection: this.name,
340
+ score: 0.9,
341
+ doc: {
342
+ id: "1",
343
+ _qdrant_id: 1,
344
+ _qdrant_vector: [0.1, 0.2],
345
+ _qdrant_vector_title: [0.3, 0.4],
346
+ tenant: "acme",
347
+ hidden: "value",
348
+ },
349
+ },
350
+ ],
351
+ };
352
+ };
353
+ const client = new QdrantCompatClient(fake);
354
+
355
+ const response = await client.queryPoints("docs", {
356
+ query: [0.1, 0.2],
357
+ limit: 1,
358
+ withPayload: ["tenant"],
359
+ withVectors: ["title"],
360
+ });
361
+
362
+ assert.deepEqual(response.points[0].payload, { tenant: "acme" });
363
+ assert.deepEqual(response.points[0].vector, { title: [0.3, 0.4] });
364
+ assert.deepEqual(collection.queries[0].fields, {
365
+ include: ["_qdrant_id", "tenant", "_qdrant_vector_title"],
366
+ });
367
+ assert.equal(collection.queries[0].includeVectors, true);
368
+
369
+ const records = await client.retrieve("docs", {
370
+ ids: [1],
371
+ withPayload: ["tenant"],
372
+ withVectors: false,
373
+ });
374
+ assert.deepEqual(records[0].payload, { tenant: "acme" });
375
+ assert.equal(records[0].vector, undefined);
376
+ assert.deepEqual(collection.docs.fetches.at(-1), {
377
+ ids: ["1"],
378
+ consistentRead: true,
379
+ includeVectors: false,
380
+ fields: { include: ["_qdrant_id", "tenant"] },
381
+ });
382
+ });
383
+
331
384
  test("retrieve, delete, scroll, count, and getCollection return qdrant-style objects", async () => {
332
385
  const fake = new FakeLambdaDB();
333
386
  fake.indexConfigs = {