chromadb 3.2.2 → 3.3.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.
@@ -7,7 +7,12 @@ import {
7
7
  deserializeMetadata,
8
8
  serializeMetadata,
9
9
  } from "./utils";
10
- import { DefaultService as Api, ChecklistResponse } from "./api";
10
+ import {
11
+ AuthenticationService,
12
+ CollectionService,
13
+ SystemService,
14
+ ChecklistResponse,
15
+ } from "./api";
11
16
  import { CollectionMetadata, UserIdentity } from "./types";
12
17
  import { Collection, CollectionImpl } from "./collection";
13
18
  import { EmbeddingFunction, getEmbeddingFunction } from "./embedding-function";
@@ -207,7 +212,7 @@ export class ChromaClient {
207
212
  * @returns Promise resolving to user identity data
208
213
  */
209
214
  public async getUserIdentity(): Promise<UserIdentity> {
210
- const { data } = await Api.getUserIdentity({
215
+ const { data } = await AuthenticationService.getUserIdentity({
211
216
  client: this.apiClient,
212
217
  });
213
218
  return data;
@@ -218,7 +223,7 @@ export class ChromaClient {
218
223
  * @returns Promise resolving to the server's nanosecond heartbeat timestamp
219
224
  */
220
225
  public async heartbeat(): Promise<number> {
221
- const { data } = await Api.heartbeat({
226
+ const { data } = await SystemService.heartbeat({
222
227
  client: this.apiClient,
223
228
  });
224
229
  return data["nanosecond heartbeat"];
@@ -239,7 +244,7 @@ export class ChromaClient {
239
244
  ): Promise<Collection[]> {
240
245
  const { limit = 100, offset = 0 } = args || {};
241
246
 
242
- const { data } = await Api.listCollections({
247
+ const { data } = await CollectionService.listCollections({
243
248
  client: this.apiClient,
244
249
  path: await this._path(),
245
250
  query: { limit, offset },
@@ -282,7 +287,7 @@ export class ChromaClient {
282
287
  * @returns Promise resolving to the collection count
283
288
  */
284
289
  public async countCollections(): Promise<number> {
285
- const { data } = await Api.countCollections({
290
+ const { data } = await CollectionService.countCollections({
286
291
  client: this.apiClient,
287
292
  path: await this._path(),
288
293
  });
@@ -320,7 +325,7 @@ export class ChromaClient {
320
325
  schema,
321
326
  });
322
327
 
323
- const { data } = await Api.createCollection({
328
+ const { data } = await CollectionService.createCollection({
324
329
  client: this.apiClient,
325
330
  path: await this._path(),
326
331
  body: {
@@ -376,7 +381,7 @@ export class ChromaClient {
376
381
  name: string;
377
382
  embeddingFunction?: EmbeddingFunction;
378
383
  }): Promise<Collection> {
379
- const { data } = await Api.getCollection({
384
+ const { data } = await CollectionService.getCollection({
380
385
  client: this.apiClient,
381
386
  path: { ...(await this._path()), collection_id: name },
382
387
  });
@@ -413,7 +418,7 @@ export class ChromaClient {
413
418
  * @throws Error if the collection does not exist
414
419
  */
415
420
  public async getCollectionByCrn(crn: string): Promise<Collection> {
416
- const { data } = await Api.getCollectionByCrn({
421
+ const { data } = await CollectionService.getCollectionByCrn({
417
422
  client: this.apiClient,
418
423
  path: { crn },
419
424
  });
@@ -497,7 +502,7 @@ export class ChromaClient {
497
502
  schema,
498
503
  });
499
504
 
500
- const { data } = await Api.createCollection({
505
+ const { data } = await CollectionService.createCollection({
501
506
  client: this.apiClient,
502
507
  path: await this._path(),
503
508
  body: {
@@ -544,7 +549,7 @@ export class ChromaClient {
544
549
  * @param options.name - The name of the collection to delete
545
550
  */
546
551
  public async deleteCollection({ name }: { name: string }): Promise<void> {
547
- await Api.deleteCollection({
552
+ await CollectionService.deleteCollection({
548
553
  client: this.apiClient,
549
554
  path: { ...(await this._path()), collection_id: name },
550
555
  });
@@ -556,7 +561,7 @@ export class ChromaClient {
556
561
  * @warning This operation is irreversible and will delete all data
557
562
  */
558
563
  public async reset(): Promise<void> {
559
- await Api.reset({
564
+ await SystemService.reset({
560
565
  client: this.apiClient,
561
566
  });
562
567
  }
@@ -566,7 +571,7 @@ export class ChromaClient {
566
571
  * @returns Promise resolving to the server version string
567
572
  */
568
573
  public async version(): Promise<string> {
569
- const { data } = await Api.version({
574
+ const { data } = await SystemService.version({
570
575
  client: this.apiClient,
571
576
  });
572
577
  return data;
@@ -578,7 +583,7 @@ export class ChromaClient {
578
583
  */
579
584
  public async getPreflightChecks(): Promise<ChecklistResponse> {
580
585
  if (!this.preflightChecks) {
581
- const { data } = await Api.preFlightChecks({
586
+ const { data } = await SystemService.preFlightChecks({
582
587
  client: this.apiClient,
583
588
  });
584
589
  this.preflightChecks = data;
package/src/collection.ts CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  WhereDocument,
20
20
  } from "./types";
21
21
  import { Include, SparseVector, SearchPayload } from "./api";
22
- import { DefaultService as Api } from "./api";
22
+ import { CollectionService, RecordService } from "./api";
23
23
  import {
24
24
  validateRecordSetLengthConsistency,
25
25
  validateIDs,
@@ -747,7 +747,7 @@ export class CollectionImpl implements Collection {
747
747
  }
748
748
 
749
749
  public async count(): Promise<number> {
750
- const { data } = await Api.collectionCount({
750
+ const { data } = await RecordService.collectionCount({
751
751
  client: this.apiClient,
752
752
  path: await this.path(),
753
753
  });
@@ -778,7 +778,7 @@ export class CollectionImpl implements Collection {
778
778
 
779
779
  const preparedRecordSet = await this.prepareRecords({ recordSet });
780
780
 
781
- await Api.collectionAdd({
781
+ await RecordService.collectionAdd({
782
782
  client: this.apiClient,
783
783
  path: await this.path(),
784
784
  body: {
@@ -812,7 +812,7 @@ export class CollectionImpl implements Collection {
812
812
 
813
813
  this.validateGet(include, ids, where, whereDocument);
814
814
 
815
- const { data } = await Api.collectionGet({
815
+ const { data } = await RecordService.collectionGet({
816
816
  client: this.apiClient,
817
817
  path: await this.path(),
818
818
  body: {
@@ -875,7 +875,7 @@ export class CollectionImpl implements Collection {
875
875
  nResults,
876
876
  );
877
877
 
878
- const { data } = await Api.collectionQuery({
878
+ const { data } = await RecordService.collectionQuery({
879
879
  client: this.apiClient,
880
880
  path: await this.path(),
881
881
  body: {
@@ -923,7 +923,7 @@ export class CollectionImpl implements Collection {
923
923
  }),
924
924
  );
925
925
 
926
- const { data } = await Api.collectionSearch({
926
+ const { data } = await RecordService.collectionSearch({
927
927
  client: this.apiClient,
928
928
  path: await this.path(),
929
929
  body: {
@@ -953,12 +953,12 @@ export class CollectionImpl implements Collection {
953
953
 
954
954
  const { updateConfiguration, updateEmbeddingFunction } = configuration
955
955
  ? await processUpdateCollectionConfig({
956
- collectionName: this.name,
957
- currentConfiguration: this.configuration,
958
- newConfiguration: configuration,
959
- currentEmbeddingFunction: this.embeddingFunction,
960
- client: this.chromaClient,
961
- })
956
+ collectionName: this.name,
957
+ currentConfiguration: this.configuration,
958
+ newConfiguration: configuration,
959
+ currentEmbeddingFunction: this.embeddingFunction,
960
+ client: this.chromaClient,
961
+ })
962
962
  : {};
963
963
 
964
964
  if (updateEmbeddingFunction) {
@@ -973,7 +973,7 @@ export class CollectionImpl implements Collection {
973
973
  };
974
974
  }
975
975
 
976
- await Api.updateCollection({
976
+ await CollectionService.updateCollection({
977
977
  client: this.apiClient,
978
978
  path: await this.path(),
979
979
  body: {
@@ -985,7 +985,7 @@ export class CollectionImpl implements Collection {
985
985
  }
986
986
 
987
987
  public async fork({ name }: { name: string }): Promise<Collection> {
988
- const { data } = await Api.forkCollection({
988
+ const { data } = await CollectionService.forkCollection({
989
989
  client: this.apiClient,
990
990
  path: await this.path(),
991
991
  body: { new_name: name },
@@ -1030,7 +1030,7 @@ export class CollectionImpl implements Collection {
1030
1030
  update: true,
1031
1031
  });
1032
1032
 
1033
- await Api.collectionUpdate({
1033
+ await RecordService.collectionUpdate({
1034
1034
  client: this.apiClient,
1035
1035
  path: await this.path(),
1036
1036
  body: {
@@ -1068,7 +1068,7 @@ export class CollectionImpl implements Collection {
1068
1068
  recordSet,
1069
1069
  });
1070
1070
 
1071
- await Api.collectionUpsert({
1071
+ await RecordService.collectionUpsert({
1072
1072
  client: this.apiClient,
1073
1073
  path: await this.path(),
1074
1074
  body: {
@@ -1092,7 +1092,7 @@ export class CollectionImpl implements Collection {
1092
1092
  }): Promise<void> {
1093
1093
  this.validateDelete(ids, where, whereDocument);
1094
1094
 
1095
- await Api.collectionDelete({
1095
+ await RecordService.collectionDelete({
1096
1096
  client: this.apiClient,
1097
1097
  path: await this.path(),
1098
1098
  body: {
@@ -1104,7 +1104,7 @@ export class CollectionImpl implements Collection {
1104
1104
  }
1105
1105
 
1106
1106
  public async getIndexingStatus(): Promise<IndexingStatus> {
1107
- const { data } = await Api.indexingStatus({
1107
+ const { data } = await RecordService.indexingStatus({
1108
1108
  client: this.apiClient,
1109
1109
  path: await this.path(),
1110
1110
  });
@@ -46,16 +46,37 @@ export class Key {
46
46
  return createComparisonWhere(this.name, "$nin", array);
47
47
  }
48
48
 
49
- public contains(value: string): WhereExpression {
50
- if (typeof value !== "string") {
51
- throw new TypeError("$contains requires a string value");
49
+ /**
50
+ * Contains filter.
51
+ *
52
+ * On `Key.DOCUMENT`: substring search (value must be a string).
53
+ * On metadata fields: checks if the array field contains the scalar value.
54
+ *
55
+ * @example
56
+ * K.DOCUMENT.contains("machine learning") // document substring
57
+ * K("tags").contains("action") // metadata array contains
58
+ * K("scores").contains(42) // metadata array contains
59
+ */
60
+ public contains(value: string | number | boolean): WhereExpression {
61
+ if (this.name === "#document" && typeof value !== "string") {
62
+ throw new TypeError("K.DOCUMENT.contains requires a string value");
52
63
  }
53
64
  return createComparisonWhere(this.name, "$contains", value);
54
65
  }
55
66
 
56
- public notContains(value: string): WhereExpression {
57
- if (typeof value !== "string") {
58
- throw new TypeError("$not_contains requires a string value");
67
+ /**
68
+ * Not-contains filter.
69
+ *
70
+ * On `Key.DOCUMENT`: excludes documents containing the substring.
71
+ * On metadata fields: checks that the array field does not contain the scalar value.
72
+ *
73
+ * @example
74
+ * K.DOCUMENT.notContains("deprecated") // document substring exclusion
75
+ * K("tags").notContains("draft") // metadata array not-contains
76
+ */
77
+ public notContains(value: string | number | boolean): WhereExpression {
78
+ if (this.name === "#document" && typeof value !== "string") {
79
+ throw new TypeError("K.DOCUMENT.notContains requires a string value");
59
80
  }
60
81
  return createComparisonWhere(this.name, "$not_contains", value);
61
82
  }
package/src/types.ts CHANGED
@@ -30,22 +30,41 @@ export type ReadLevel = (typeof ReadLevel)[keyof typeof ReadLevel];
30
30
  */
31
31
  export type { SparseVector };
32
32
 
33
+ /**
34
+ * Scalar metadata values that can be stored in arrays.
35
+ */
36
+ export type MetadataScalar = boolean | number | string;
37
+
33
38
  /**
34
39
  * Metadata that can be associated with a collection.
35
- * Values must be boolean, number, or string types.
40
+ * Values can be boolean, number, string, SparseVector, typed arrays, or null.
36
41
  */
37
42
  export type CollectionMetadata = Record<
38
43
  string,
39
- boolean | number | string | SparseVector | null
44
+ | boolean
45
+ | number
46
+ | string
47
+ | SparseVector
48
+ | boolean[]
49
+ | number[]
50
+ | string[]
51
+ | null
40
52
  >;
41
53
 
42
54
  /**
43
55
  * Metadata that can be associated with individual records.
44
- * Values must be boolean, number, or string types.
56
+ * Values can be boolean, number, string, SparseVector, typed arrays, or null.
45
57
  */
46
58
  export type Metadata = Record<
47
59
  string,
48
- boolean | number | string | SparseVector | null
60
+ | boolean
61
+ | number
62
+ | string
63
+ | SparseVector
64
+ | boolean[]
65
+ | number[]
66
+ | string[]
67
+ | null
49
68
  >;
50
69
 
51
70
  /**
@@ -106,6 +125,8 @@ type WhereOperator = "$gt" | "$gte" | "$lt" | "$lte" | "$ne" | "$eq";
106
125
 
107
126
  type InclusionExclusionOperator = "$in" | "$nin";
108
127
 
128
+ type ArrayContainsOperator = "$contains" | "$not_contains";
129
+
109
130
  type OperatorExpression =
110
131
  | { $gt: LiteralValue }
111
132
  | { $gte: LiteralValue }
@@ -116,7 +137,9 @@ type OperatorExpression =
116
137
  | { $and: LiteralValue }
117
138
  | { $or: LiteralValue }
118
139
  | { $in: LiteralValue[] }
119
- | { $nin: LiteralValue[] };
140
+ | { $nin: LiteralValue[] }
141
+ | { $contains: LiteralValue }
142
+ | { $not_contains: LiteralValue };
120
143
 
121
144
  /**
122
145
  * Where clause for filtering records based on metadata.
package/src/utils.ts CHANGED
@@ -260,19 +260,50 @@ export const validateMetadata = (metadata?: Metadata) => {
260
260
  throw new ChromaValueError("Expected metadata to be non-empty");
261
261
  }
262
262
 
263
- if (
264
- !Object.values(metadata).every(
265
- (v: any) =>
266
- v === null ||
267
- v === undefined ||
268
- typeof v === "string" ||
269
- typeof v === "number" ||
270
- typeof v === "boolean" ||
271
- validateSparseVector(v),
272
- )
273
- ) {
263
+ const validateMetadataListValue = (key: string, v: unknown[]): void => {
264
+ if (v.length === 0) {
265
+ throw new ChromaValueError(
266
+ `Expected metadata list value for key '${key}' to be non-empty`,
267
+ );
268
+ }
269
+ const firstType = typeof v[0];
270
+ for (const item of v) {
271
+ if (
272
+ typeof item !== "string" &&
273
+ typeof item !== "number" &&
274
+ typeof item !== "boolean"
275
+ ) {
276
+ throw new ChromaValueError(
277
+ `Expected metadata list value for key '${key}' to contain only strings, numbers, or booleans, got ${typeof item}`,
278
+ );
279
+ }
280
+ if (typeof item !== firstType) {
281
+ throw new ChromaValueError(
282
+ `Expected metadata list value for key '${key}' to contain only the same type, got mixed types`,
283
+ );
284
+ }
285
+ }
286
+ };
287
+
288
+ for (const [key, v] of Object.entries(metadata)) {
289
+ if (
290
+ v === null ||
291
+ v === undefined ||
292
+ typeof v === "string" ||
293
+ typeof v === "number" ||
294
+ typeof v === "boolean"
295
+ ) {
296
+ continue;
297
+ }
298
+ if (validateSparseVector(v)) {
299
+ continue;
300
+ }
301
+ if (Array.isArray(v)) {
302
+ validateMetadataListValue(key, v);
303
+ continue;
304
+ }
274
305
  throw new ChromaValueError(
275
- "Expected metadata to be a string, number, boolean, SparseVector, or nullable",
306
+ `Expected metadata value for key '${key}' to be a string, number, boolean, SparseVector, typed array (string[], number[], boolean[]), or null`,
276
307
  );
277
308
  }
278
309
  };
@@ -289,6 +320,9 @@ type SerializedMetadataValue =
289
320
  | string
290
321
  | SerializedSparseVector
291
322
  | SparseVector
323
+ | Array<boolean>
324
+ | Array<number>
325
+ | Array<string>
292
326
  | null;
293
327
 
294
328
  export type SerializedMetadata = Record<string, SerializedMetadataValue>;
@@ -315,10 +349,13 @@ export const serializeMetadata = (
315
349
  const result: SerializedMetadata = {};
316
350
 
317
351
  Object.entries(metadata).forEach(([key, value]) => {
352
+ if (value === null || value === undefined) {
353
+ return;
354
+ }
318
355
  if (validateSparseVector(value)) {
319
356
  result[key] = toSerializedSparseVector(value);
320
357
  } else {
321
- result[key] = value ?? null;
358
+ result[key] = value;
322
359
  }
323
360
  });
324
361
 
@@ -555,12 +592,30 @@ export const validateWhere = (where: Where) => {
555
592
  }
556
593
 
557
594
  if (
558
- !["$gt", "$gte", "$lt", "$lte", "$ne", "$eq", "$in", "$nin"].includes(
559
- operator,
560
- )
595
+ ["$contains", "$not_contains"].includes(operator) &&
596
+ !["string", "number", "boolean"].includes(typeof operand)
597
+ ) {
598
+ throw new ChromaValueError(
599
+ `Expected operand value to be a string, number, or boolean for ${operator}, but got ${typeof operand}`,
600
+ );
601
+ }
602
+
603
+ if (
604
+ ![
605
+ "$gt",
606
+ "$gte",
607
+ "$lt",
608
+ "$lte",
609
+ "$ne",
610
+ "$eq",
611
+ "$in",
612
+ "$nin",
613
+ "$contains",
614
+ "$not_contains",
615
+ ].includes(operator)
561
616
  ) {
562
617
  throw new ChromaValueError(
563
- `Expected operator to be one of $gt, $gte, $lt, $lte, $ne, $eq, $in, $nin, but got ${operator}`,
618
+ `Expected operator to be one of $gt, $gte, $lt, $lte, $ne, $eq, $in, $nin, $contains, $not_contains, but got ${operator}`,
564
619
  );
565
620
  }
566
621