@mastra/chroma 0.10.3 → 0.10.4-alpha.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.
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
1
2
  import { MastraVector } from '@mastra/core/vector';
2
3
  import { ChromaClient } from 'chromadb';
3
4
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
@@ -123,18 +124,31 @@ var ChromaVector = class extends MastraVector {
123
124
  }
124
125
  }
125
126
  async upsert({ indexName, vectors, metadata, ids, documents }) {
126
- const collection = await this.getCollection(indexName);
127
- const stats = await this.describeIndex({ indexName });
128
- this.validateVectorDimensions(vectors, stats.dimension);
129
- const generatedIds = ids || vectors.map(() => crypto.randomUUID());
130
- const normalizedMetadata = metadata || vectors.map(() => ({}));
131
- await collection.upsert({
132
- ids: generatedIds,
133
- embeddings: vectors,
134
- metadatas: normalizedMetadata,
135
- documents
136
- });
137
- return generatedIds;
127
+ try {
128
+ const collection = await this.getCollection(indexName);
129
+ const stats = await this.describeIndex({ indexName });
130
+ this.validateVectorDimensions(vectors, stats.dimension);
131
+ const generatedIds = ids || vectors.map(() => crypto.randomUUID());
132
+ const normalizedMetadata = metadata || vectors.map(() => ({}));
133
+ await collection.upsert({
134
+ ids: generatedIds,
135
+ embeddings: vectors,
136
+ metadatas: normalizedMetadata,
137
+ documents
138
+ });
139
+ return generatedIds;
140
+ } catch (error) {
141
+ if (error instanceof MastraError) throw error;
142
+ throw new MastraError(
143
+ {
144
+ id: "CHROMA_VECTOR_UPSERT_FAILED",
145
+ domain: ErrorDomain.MASTRA_VECTOR,
146
+ category: ErrorCategory.THIRD_PARTY,
147
+ details: { indexName }
148
+ },
149
+ error
150
+ );
151
+ }
138
152
  }
139
153
  HnswSpaceMap = {
140
154
  cosine: "cosine",
@@ -145,11 +159,23 @@ var ChromaVector = class extends MastraVector {
145
159
  };
146
160
  async createIndex({ indexName, dimension, metric = "cosine" }) {
147
161
  if (!Number.isInteger(dimension) || dimension <= 0) {
148
- throw new Error("Dimension must be a positive integer");
162
+ throw new MastraError({
163
+ id: "CHROMA_VECTOR_CREATE_INDEX_INVALID_DIMENSION",
164
+ text: "Dimension must be a positive integer",
165
+ domain: ErrorDomain.MASTRA_VECTOR,
166
+ category: ErrorCategory.USER,
167
+ details: { dimension }
168
+ });
149
169
  }
150
170
  const hnswSpace = this.HnswSpaceMap[metric];
151
- if (!["cosine", "l2", "ip"].includes(hnswSpace)) {
152
- throw new Error(`Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`);
171
+ if (!hnswSpace || !["cosine", "l2", "ip"].includes(hnswSpace)) {
172
+ throw new MastraError({
173
+ id: "CHROMA_VECTOR_CREATE_INDEX_INVALID_METRIC",
174
+ text: `Invalid metric: "${metric}". Must be one of: cosine, euclidean, dotproduct`,
175
+ domain: ErrorDomain.MASTRA_VECTOR,
176
+ category: ErrorCategory.USER,
177
+ details: { metric }
178
+ });
153
179
  }
154
180
  try {
155
181
  await this.client.createCollection({
@@ -165,7 +191,15 @@ var ChromaVector = class extends MastraVector {
165
191
  await this.validateExistingIndex(indexName, dimension, metric);
166
192
  return;
167
193
  }
168
- throw error;
194
+ throw new MastraError(
195
+ {
196
+ id: "CHROMA_VECTOR_CREATE_INDEX_FAILED",
197
+ domain: ErrorDomain.MASTRA_VECTOR,
198
+ category: ErrorCategory.THIRD_PARTY,
199
+ details: { indexName }
200
+ },
201
+ error
202
+ );
169
203
  }
170
204
  }
171
205
  transformFilter(filter) {
@@ -180,27 +214,51 @@ var ChromaVector = class extends MastraVector {
180
214
  includeVector = false,
181
215
  documentFilter
182
216
  }) {
183
- const collection = await this.getCollection(indexName, true);
184
- const defaultInclude = ["documents", "metadatas", "distances"];
185
- const translatedFilter = this.transformFilter(filter);
186
- const results = await collection.query({
187
- queryEmbeddings: [queryVector],
188
- nResults: topK,
189
- where: translatedFilter,
190
- whereDocument: documentFilter,
191
- include: includeVector ? [...defaultInclude, "embeddings"] : defaultInclude
192
- });
193
- return (results.ids[0] || []).map((id, index) => ({
194
- id,
195
- score: results.distances?.[0]?.[index] || 0,
196
- metadata: results.metadatas?.[0]?.[index] || {},
197
- document: results.documents?.[0]?.[index],
198
- ...includeVector && { vector: results.embeddings?.[0]?.[index] || [] }
199
- }));
217
+ try {
218
+ const collection = await this.getCollection(indexName, true);
219
+ const defaultInclude = ["documents", "metadatas", "distances"];
220
+ const translatedFilter = this.transformFilter(filter);
221
+ const results = await collection.query({
222
+ queryEmbeddings: [queryVector],
223
+ nResults: topK,
224
+ where: translatedFilter,
225
+ whereDocument: documentFilter,
226
+ include: includeVector ? [...defaultInclude, "embeddings"] : defaultInclude
227
+ });
228
+ return (results.ids[0] || []).map((id, index) => ({
229
+ id,
230
+ score: results.distances?.[0]?.[index] || 0,
231
+ metadata: results.metadatas?.[0]?.[index] || {},
232
+ document: results.documents?.[0]?.[index],
233
+ ...includeVector && { vector: results.embeddings?.[0]?.[index] || [] }
234
+ }));
235
+ } catch (error) {
236
+ if (error instanceof MastraError) throw error;
237
+ throw new MastraError(
238
+ {
239
+ id: "CHROMA_VECTOR_QUERY_FAILED",
240
+ domain: ErrorDomain.MASTRA_VECTOR,
241
+ category: ErrorCategory.THIRD_PARTY,
242
+ details: { indexName }
243
+ },
244
+ error
245
+ );
246
+ }
200
247
  }
201
248
  async listIndexes() {
202
- const collections = await this.client.listCollections();
203
- return collections.map((collection) => collection);
249
+ try {
250
+ const collections = await this.client.listCollections();
251
+ return collections.map((collection) => collection);
252
+ } catch (error) {
253
+ throw new MastraError(
254
+ {
255
+ id: "CHROMA_VECTOR_LIST_INDEXES_FAILED",
256
+ domain: ErrorDomain.MASTRA_VECTOR,
257
+ category: ErrorCategory.THIRD_PARTY
258
+ },
259
+ error
260
+ );
261
+ }
204
262
  }
205
263
  /**
206
264
  * Retrieves statistics about a vector index.
@@ -209,19 +267,44 @@ var ChromaVector = class extends MastraVector {
209
267
  * @returns A promise that resolves to the index statistics including dimension, count and metric
210
268
  */
211
269
  async describeIndex({ indexName }) {
212
- const collection = await this.getCollection(indexName);
213
- const count = await collection.count();
214
- const metadata = collection.metadata;
215
- const hnswSpace = metadata?.["hnsw:space"];
216
- return {
217
- dimension: metadata?.dimension || 0,
218
- count,
219
- metric: this.HnswSpaceMap[hnswSpace]
220
- };
270
+ try {
271
+ const collection = await this.getCollection(indexName);
272
+ const count = await collection.count();
273
+ const metadata = collection.metadata;
274
+ const hnswSpace = metadata?.["hnsw:space"];
275
+ return {
276
+ dimension: metadata?.dimension || 0,
277
+ count,
278
+ metric: this.HnswSpaceMap[hnswSpace]
279
+ };
280
+ } catch (error) {
281
+ if (error instanceof MastraError) throw error;
282
+ throw new MastraError(
283
+ {
284
+ id: "CHROMA_VECTOR_DESCRIBE_INDEX_FAILED",
285
+ domain: ErrorDomain.MASTRA_VECTOR,
286
+ category: ErrorCategory.THIRD_PARTY,
287
+ details: { indexName }
288
+ },
289
+ error
290
+ );
291
+ }
221
292
  }
222
293
  async deleteIndex({ indexName }) {
223
- await this.client.deleteCollection({ name: indexName });
224
- this.collections.delete(indexName);
294
+ try {
295
+ await this.client.deleteCollection({ name: indexName });
296
+ this.collections.delete(indexName);
297
+ } catch (error) {
298
+ throw new MastraError(
299
+ {
300
+ id: "CHROMA_VECTOR_DELETE_INDEX_FAILED",
301
+ domain: ErrorDomain.MASTRA_VECTOR,
302
+ category: ErrorCategory.THIRD_PARTY,
303
+ details: { indexName }
304
+ },
305
+ error
306
+ );
307
+ }
225
308
  }
226
309
  /**
227
310
  * Updates a vector by its ID with the provided vector and/or metadata.
@@ -234,10 +317,16 @@ var ChromaVector = class extends MastraVector {
234
317
  * @throws Will throw an error if no updates are provided or if the update operation fails.
235
318
  */
236
319
  async updateVector({ indexName, id, update }) {
320
+ if (!update.vector && !update.metadata) {
321
+ throw new MastraError({
322
+ id: "CHROMA_VECTOR_UPDATE_NO_PAYLOAD",
323
+ text: "No updates provided for vector",
324
+ domain: ErrorDomain.MASTRA_VECTOR,
325
+ category: ErrorCategory.USER,
326
+ details: { indexName, id }
327
+ });
328
+ }
237
329
  try {
238
- if (!update.vector && !update.metadata) {
239
- throw new Error("No updates provided");
240
- }
241
330
  const collection = await this.getCollection(indexName, true);
242
331
  const updateOptions = { ids: [id] };
243
332
  if (update?.vector) {
@@ -250,22 +339,33 @@ var ChromaVector = class extends MastraVector {
250
339
  }
251
340
  return await collection.update(updateOptions);
252
341
  } catch (error) {
253
- throw new Error(`Failed to update vector by id: ${id} for index name: ${indexName}: ${error.message}`);
342
+ if (error instanceof MastraError) throw error;
343
+ throw new MastraError(
344
+ {
345
+ id: "CHROMA_VECTOR_UPDATE_FAILED",
346
+ domain: ErrorDomain.MASTRA_VECTOR,
347
+ category: ErrorCategory.THIRD_PARTY,
348
+ details: { indexName, id }
349
+ },
350
+ error
351
+ );
254
352
  }
255
353
  }
256
- /**
257
- * Deletes a vector by its ID.
258
- * @param indexName - The name of the index containing the vector.
259
- * @param id - The ID of the vector to delete.
260
- * @returns A promise that resolves when the deletion is complete.
261
- * @throws Will throw an error if the deletion operation fails.
262
- */
263
354
  async deleteVector({ indexName, id }) {
264
355
  try {
265
356
  const collection = await this.getCollection(indexName, true);
266
357
  await collection.delete({ ids: [id] });
267
358
  } catch (error) {
268
- throw new Error(`Failed to delete vector by id: ${id} for index name: ${indexName}: ${error.message}`);
359
+ if (error instanceof MastraError) throw error;
360
+ throw new MastraError(
361
+ {
362
+ id: "CHROMA_VECTOR_DELETE_FAILED",
363
+ domain: ErrorDomain.MASTRA_VECTOR,
364
+ category: ErrorCategory.THIRD_PARTY,
365
+ details: { indexName, id }
366
+ },
367
+ error
368
+ );
269
369
  }
270
370
  }
271
371
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/chroma",
3
- "version": "0.10.3",
3
+ "version": "0.10.4-alpha.1",
4
4
  "description": "Chroma vector store provider for Mastra",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,12 +25,12 @@
25
25
  "devDependencies": {
26
26
  "@microsoft/api-extractor": "^7.52.8",
27
27
  "@types/node": "^20.19.0",
28
- "eslint": "^9.28.0",
28
+ "eslint": "^9.29.0",
29
29
  "tsup": "^8.5.0",
30
30
  "typescript": "^5.8.3",
31
31
  "vitest": "^3.2.3",
32
32
  "@internal/lint": "0.0.13",
33
- "@mastra/core": "0.10.6"
33
+ "@mastra/core": "0.10.7-alpha.2"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@mastra/core": ">=0.10.4-0 <0.11.0"
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
2
 
3
+ import type { ChromaVectorFilter } from './filter';
3
4
  import { ChromaFilterTranslator } from './filter';
4
5
 
5
6
  describe('ChromaFilterTranslator', () => {
@@ -13,17 +14,17 @@ describe('ChromaFilterTranslator', () => {
13
14
  describe('basic operations', () => {
14
15
  it('handles empty filters', () => {
15
16
  expect(translator.translate({})).toEqual({});
16
- expect(translator.translate(null as any)).toEqual(null);
17
- expect(translator.translate(undefined as any)).toEqual(undefined);
17
+ expect(translator.translate(null)).toEqual(null);
18
+ expect(translator.translate(undefined)).toEqual(undefined);
18
19
  });
19
20
 
20
21
  it('retains implicit equality', () => {
21
- const filter = { field: 'value' };
22
+ const filter: ChromaVectorFilter = { field: 'value' };
22
23
  expect(translator.translate(filter)).toEqual({ field: 'value' });
23
24
  });
24
25
 
25
26
  it('converts multiple top-level fields to $and', () => {
26
- const filter = {
27
+ const filter: ChromaVectorFilter = {
27
28
  field1: 'value1',
28
29
  field2: 'value2',
29
30
  };
@@ -33,7 +34,7 @@ describe('ChromaFilterTranslator', () => {
33
34
  });
34
35
 
35
36
  it('handles multiple operators on same field', () => {
36
- const filter = {
37
+ const filter: ChromaVectorFilter = {
37
38
  price: { $gt: 100, $lt: 200 },
38
39
  quantity: { $gte: 10, $lte: 20 },
39
40
  };
@@ -49,7 +50,7 @@ describe('ChromaFilterTranslator', () => {
49
50
 
50
51
  it('normalizes date values', () => {
51
52
  const date = new Date('2024-01-01');
52
- const filter = { timestamp: { $gt: date } };
53
+ const filter: ChromaVectorFilter = { timestamp: { $gt: date } };
53
54
  expect(translator.translate(filter)).toEqual({ timestamp: { $gt: date.toISOString() } });
54
55
  });
55
56
  });
@@ -57,7 +58,7 @@ describe('ChromaFilterTranslator', () => {
57
58
  // Array Operations
58
59
  describe('array operations', () => {
59
60
  it('handles arrays as $in operator', () => {
60
- const filter = { tags: ['tag1', 'tag2'] };
61
+ const filter: ChromaVectorFilter = { tags: ['tag1', 'tag2'] };
61
62
  expect(translator.translate(filter)).toEqual({ tags: { $in: ['tag1', 'tag2'] } });
62
63
  });
63
64
 
@@ -68,10 +69,14 @@ describe('ChromaFilterTranslator', () => {
68
69
 
69
70
  it('handles arrays as direct values', () => {
70
71
  // Direct array value should be converted to $in
71
- expect(translator.translate({ field: ['value1', 'value2'] })).toEqual({ field: { $in: ['value1', 'value2'] } });
72
+ const filter: ChromaVectorFilter = { field: ['value1', 'value2'] };
73
+ expect(translator.translate(filter)).toEqual({
74
+ field: { $in: ['value1', 'value2'] },
75
+ });
72
76
 
73
77
  // Empty direct array
74
- expect(translator.translate({ field: [] })).toEqual({ field: { $in: [] } });
78
+ const filter2 = { field: [] };
79
+ expect(translator.translate(filter2)).toEqual({ field: { $in: [] } });
75
80
  });
76
81
 
77
82
  describe('$in operator variations', () => {
@@ -99,7 +104,7 @@ describe('ChromaFilterTranslator', () => {
99
104
  // Logical Operators
100
105
  describe('logical operators', () => {
101
106
  it('handles logical operators', () => {
102
- const filter = {
107
+ const filter: ChromaVectorFilter = {
103
108
  $or: [{ status: { $eq: 'active' } }, { age: { $gt: 25 } }],
104
109
  };
105
110
  expect(translator.translate(filter)).toEqual({
@@ -108,7 +113,7 @@ describe('ChromaFilterTranslator', () => {
108
113
  });
109
114
 
110
115
  it('handles nested logical operators', () => {
111
- const filter = {
116
+ const filter: ChromaVectorFilter = {
112
117
  $and: [
113
118
  { status: { $eq: 'active' } },
114
119
  {
@@ -142,7 +147,7 @@ describe('ChromaFilterTranslator', () => {
142
147
  });
143
148
 
144
149
  it('handles complex nested conditions', () => {
145
- const filter = {
150
+ const filter: ChromaVectorFilter = {
146
151
  $or: [
147
152
  { age: { $gt: 25 } },
148
153
  {
@@ -176,7 +181,7 @@ describe('ChromaFilterTranslator', () => {
176
181
  });
177
182
 
178
183
  it('preserves empty objects as exact match conditions', () => {
179
- const filter = {
184
+ const filter: ChromaVectorFilter = {
180
185
  metadata: {},
181
186
  'user.profile': {},
182
187
  };
@@ -187,7 +192,7 @@ describe('ChromaFilterTranslator', () => {
187
192
  });
188
193
 
189
194
  it('handles empty objects in logical operators', () => {
190
- const filter = {
195
+ const filter: ChromaVectorFilter = {
191
196
  $or: [{}, { status: 'active' }],
192
197
  };
193
198
 
@@ -211,7 +216,7 @@ describe('ChromaFilterTranslator', () => {
211
216
  });
212
217
 
213
218
  it('handles empty objects in comparison operators', () => {
214
- const filter = {
219
+ const filter: ChromaVectorFilter = {
215
220
  metadata: { $eq: {} },
216
221
  };
217
222
 
@@ -221,7 +226,7 @@ describe('ChromaFilterTranslator', () => {
221
226
  });
222
227
 
223
228
  it('handles empty objects in array operators', () => {
224
- const filter = {
229
+ const filter: ChromaVectorFilter = {
225
230
  tags: { $in: [{}] },
226
231
  };
227
232
 
@@ -335,7 +340,7 @@ describe('ChromaFilterTranslator', () => {
335
340
  });
336
341
 
337
342
  it('throws error for unsupported logical operators', () => {
338
- const invalidFilters = [
343
+ const invalidFilters: any = [
339
344
  {
340
345
  $not: { field: 'value' },
341
346
  },
@@ -374,7 +379,7 @@ describe('ChromaFilterTranslator', () => {
374
379
  });
375
380
 
376
381
  it('throws error for unsupported operators', () => {
377
- const unsupportedFilters = [
382
+ const unsupportedFilters: any = [
378
383
  { field: { $regex: 'pattern' } },
379
384
  { field: { $contains: 'value' } },
380
385
  { field: { $exists: true } },
@@ -395,7 +400,7 @@ describe('ChromaFilterTranslator', () => {
395
400
  expect(() => translator.translate(filter)).toThrow();
396
401
  });
397
402
  it('throws error for non-logical operators at top level', () => {
398
- const invalidFilters = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
403
+ const invalidFilters: any = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
399
404
 
400
405
  invalidFilters.forEach(filter => {
401
406
  expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
@@ -1,12 +1,43 @@
1
1
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
2
- import type { FieldCondition, VectorFilter, OperatorSupport, QueryOperator } from '@mastra/core/vector/filter';
2
+ import type {
3
+ VectorFilter,
4
+ OperatorSupport,
5
+ QueryOperator,
6
+ OperatorValueMap,
7
+ LogicalOperatorValueMap,
8
+ BlacklistedRootOperators,
9
+ } from '@mastra/core/vector/filter';
10
+
11
+ type ChromaOperatorValueMap = Omit<OperatorValueMap, '$exists' | '$elemMatch' | '$regex' | '$options'>;
12
+
13
+ type ChromaLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor' | '$not'>;
14
+
15
+ type ChromaBlacklisted = BlacklistedRootOperators | '$nor' | '$not';
16
+
17
+ export type ChromaVectorFilter = VectorFilter<
18
+ keyof ChromaOperatorValueMap,
19
+ ChromaOperatorValueMap,
20
+ ChromaLogicalOperatorValueMap,
21
+ ChromaBlacklisted
22
+ >;
23
+
24
+ type ChromaDocumentOperatorValueMap = ChromaOperatorValueMap;
25
+
26
+ type ChromaDocumentBlacklisted = Exclude<ChromaBlacklisted, '$contains'>;
27
+
28
+ export type ChromaVectorDocumentFilter = VectorFilter<
29
+ keyof ChromaDocumentOperatorValueMap,
30
+ ChromaDocumentOperatorValueMap,
31
+ ChromaLogicalOperatorValueMap,
32
+ ChromaDocumentBlacklisted
33
+ >;
3
34
 
4
35
  /**
5
36
  * Translator for Chroma filter queries.
6
37
  * Maintains MongoDB-compatible syntax while ensuring proper validation
7
38
  * and normalization of values.
8
39
  */
9
- export class ChromaFilterTranslator extends BaseFilterTranslator {
40
+ export class ChromaFilterTranslator extends BaseFilterTranslator<ChromaVectorFilter> {
10
41
  protected override getSupportedOperators(): OperatorSupport {
11
42
  return {
12
43
  ...BaseFilterTranslator.DEFAULT_OPERATORS,
@@ -18,14 +49,14 @@ export class ChromaFilterTranslator extends BaseFilterTranslator {
18
49
  };
19
50
  }
20
51
 
21
- translate(filter?: VectorFilter): VectorFilter {
52
+ translate(filter?: ChromaVectorFilter): ChromaVectorFilter {
22
53
  if (this.isEmpty(filter)) return filter;
23
54
  this.validateFilter(filter);
24
55
 
25
56
  return this.translateNode(filter);
26
57
  }
27
58
 
28
- private translateNode(node: VectorFilter | FieldCondition, currentPath: string = ''): any {
59
+ private translateNode(node: ChromaVectorFilter, currentPath: string = ''): any {
29
60
  // Handle primitive values and arrays
30
61
  if (this.isRegex(node)) {
31
62
  throw new Error('Regex is not supported in Chroma');
@@ -378,7 +378,7 @@ describe('ChromaVector Integration Tests', () => {
378
378
  indexName: testIndexName,
379
379
  queryVector: [1, 0, 0],
380
380
  filter: {
381
- tags: { $in: null },
381
+ tags: { $in: null } as any,
382
382
  },
383
383
  }),
384
384
  ).rejects.toThrow();
@@ -479,9 +479,9 @@ describe('ChromaVector Integration Tests', () => {
479
479
  indexName: testIndexName,
480
480
  queryVector: [1, 0, 0],
481
481
  filter: {
482
- field1: { $in: 'not-array' },
483
- field2: { $exists: 'not-boolean' },
484
- field3: { $gt: 'not-number' },
482
+ field1: { $in: 'not-array' } as any,
483
+ field2: { $exists: 'not-boolean' } as any,
484
+ field3: { $gt: 'not-number' } as any,
485
485
  },
486
486
  }),
487
487
  ).rejects.toThrow();