@mastra/chroma 0.1.6-alpha.0 → 0.1.6-alpha.3
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/.turbo/turbo-build.log +11 -6
- package/CHANGELOG.md +37 -0
- package/README.md +26 -16
- package/dist/_tsup-dts-rollup.d.cts +56 -0
- package/dist/_tsup-dts-rollup.d.ts +24 -11
- package/dist/index.cjs +214 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.js +31 -11
- package/docker-compose.yaml +7 -0
- package/eslint.config.js +6 -0
- package/package.json +12 -5
- package/src/vector/filter.ts +5 -10
- package/src/vector/index.test.ts +687 -192
- package/src/vector/index.ts +57 -29
package/src/vector/index.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { QueryResult, IndexStats } from '@mastra/core/vector';
|
|
2
|
-
import { describe, expect, beforeEach, afterEach, it, beforeAll, afterAll } from 'vitest';
|
|
1
|
+
import type { QueryResult, IndexStats } from '@mastra/core/vector';
|
|
2
|
+
import { describe, expect, beforeEach, afterEach, it, beforeAll, afterAll, vi } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import { ChromaVector } from './';
|
|
5
5
|
|
|
@@ -10,23 +10,24 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
10
10
|
|
|
11
11
|
const testIndexName = 'test-index';
|
|
12
12
|
const testIndexName2 = 'test-index-2';
|
|
13
|
+
const testIndexName3 = 'test-index-3';
|
|
13
14
|
const dimension = 3;
|
|
14
15
|
|
|
15
16
|
beforeEach(async () => {
|
|
16
17
|
// Clean up any existing test index
|
|
17
18
|
try {
|
|
18
19
|
await vectorDB.deleteIndex(testIndexName);
|
|
19
|
-
} catch
|
|
20
|
+
} catch {
|
|
20
21
|
// Ignore errors if index doesn't exist
|
|
21
22
|
}
|
|
22
|
-
await vectorDB.createIndex(testIndexName, dimension);
|
|
23
|
+
await vectorDB.createIndex({ indexName: testIndexName, dimension });
|
|
23
24
|
}, 5000);
|
|
24
25
|
|
|
25
26
|
afterEach(async () => {
|
|
26
27
|
// Cleanup after tests
|
|
27
28
|
try {
|
|
28
29
|
await vectorDB.deleteIndex(testIndexName);
|
|
29
|
-
} catch
|
|
30
|
+
} catch {
|
|
30
31
|
// Ignore cleanup errors
|
|
31
32
|
}
|
|
32
33
|
}, 5000);
|
|
@@ -55,7 +56,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
55
56
|
|
|
56
57
|
for (const metric of metricsToTest) {
|
|
57
58
|
const testIndex = `test-index-${metric}`;
|
|
58
|
-
await vectorDB.createIndex(testIndex, dimension, metric);
|
|
59
|
+
await vectorDB.createIndex({ indexName: testIndex, dimension, metric });
|
|
59
60
|
|
|
60
61
|
const stats = await vectorDB.describeIndex(testIndex);
|
|
61
62
|
expect(stats.metric).toBe(metric);
|
|
@@ -75,7 +76,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
75
76
|
const testIds = ['vec1', 'vec2', 'vec3'];
|
|
76
77
|
|
|
77
78
|
it('should upsert vectors with generated ids', async () => {
|
|
78
|
-
const ids = await vectorDB.upsert(testIndexName, testVectors);
|
|
79
|
+
const ids = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors });
|
|
79
80
|
expect(ids).toHaveLength(testVectors.length);
|
|
80
81
|
ids.forEach(id => expect(typeof id).toBe('string'));
|
|
81
82
|
|
|
@@ -84,14 +85,14 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
it('should upsert vectors with provided ids and metadata', async () => {
|
|
87
|
-
await vectorDB.upsert(testIndexName, testVectors, testMetadata, testIds);
|
|
88
|
+
await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
|
|
88
89
|
|
|
89
90
|
const stats = await vectorDB.describeIndex(testIndexName);
|
|
90
91
|
expect(stats.count).toBe(testVectors.length);
|
|
91
92
|
|
|
92
93
|
// Query each vector to verify metadata
|
|
93
94
|
for (let i = 0; i < testVectors.length; i++) {
|
|
94
|
-
const results = await vectorDB.query(testIndexName, testVectors?.[i]!, 1);
|
|
95
|
+
const results = await vectorDB.query({ indexName: testIndexName, queryVector: testVectors?.[i]!, topK: 1 });
|
|
95
96
|
expect(results?.[0]?.id).toBe(testIds[i]);
|
|
96
97
|
expect(results?.[0]?.metadata).toEqual(testMetadata[i]);
|
|
97
98
|
}
|
|
@@ -99,15 +100,20 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
99
100
|
|
|
100
101
|
it('should update existing vectors', async () => {
|
|
101
102
|
// Initial upsert
|
|
102
|
-
await vectorDB.upsert(testIndexName, testVectors, testMetadata, testIds);
|
|
103
|
+
await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
|
|
103
104
|
|
|
104
105
|
// Update first vector
|
|
105
106
|
const updatedVector = [[0.5, 0.5, 0.0]];
|
|
106
107
|
const updatedMetadata = [{ label: 'updated-x-axis' }];
|
|
107
|
-
await vectorDB.upsert(
|
|
108
|
+
await vectorDB.upsert({
|
|
109
|
+
indexName: testIndexName,
|
|
110
|
+
vectors: updatedVector,
|
|
111
|
+
metadata: updatedMetadata,
|
|
112
|
+
ids: [testIds?.[0]!],
|
|
113
|
+
});
|
|
108
114
|
|
|
109
115
|
// Verify update
|
|
110
|
-
const results = await vectorDB.query(testIndexName, updatedVector?.[0]!, 1);
|
|
116
|
+
const results = await vectorDB.query({ indexName: testIndexName, queryVector: updatedVector?.[0]!, topK: 1 });
|
|
111
117
|
expect(results?.[0]?.id).toBe(testIds[0]);
|
|
112
118
|
expect(results?.[0]?.metadata).toEqual(updatedMetadata[0]);
|
|
113
119
|
});
|
|
@@ -123,7 +129,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
123
129
|
const testIds = ['vec1', 'vec2', 'vec3'];
|
|
124
130
|
|
|
125
131
|
beforeEach(async () => {
|
|
126
|
-
await vectorDB.upsert(testIndexName, testVectors, testMetadata, testIds);
|
|
132
|
+
await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
|
|
127
133
|
});
|
|
128
134
|
|
|
129
135
|
describe('Basic Queries', () => {
|
|
@@ -131,7 +137,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
131
137
|
const queryVector = [1.0, 0.1, 0.1];
|
|
132
138
|
const topK = 2;
|
|
133
139
|
|
|
134
|
-
const results: QueryResult[] = await vectorDB.query(testIndexName, queryVector, topK);
|
|
140
|
+
const results: QueryResult[] = await vectorDB.query({ indexName: testIndexName, queryVector, topK });
|
|
135
141
|
|
|
136
142
|
expect(results).toHaveLength(topK);
|
|
137
143
|
expect(results?.[0]?.id).toBe(testIds[0]); // Should match x-axis vector most closely
|
|
@@ -143,7 +149,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
143
149
|
const queryVector = [1.0, 1.0, 1.0];
|
|
144
150
|
const filter = { label: 'x-axis' };
|
|
145
151
|
|
|
146
|
-
const results = await vectorDB.query(testIndexName, queryVector, 3, filter);
|
|
152
|
+
const results = await vectorDB.query({ indexName: testIndexName, queryVector, topK: 3, filter });
|
|
147
153
|
|
|
148
154
|
expect(results).toHaveLength(1);
|
|
149
155
|
expect(results?.[0]?.metadata?.label).toBe('x-axis');
|
|
@@ -155,7 +161,12 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
155
161
|
const queryVector = [1.0, 0.1, 0.1];
|
|
156
162
|
const topK = 1;
|
|
157
163
|
|
|
158
|
-
const results = await vectorDB.query(
|
|
164
|
+
const results = await vectorDB.query({
|
|
165
|
+
indexName: testIndexName,
|
|
166
|
+
queryVector,
|
|
167
|
+
topK,
|
|
168
|
+
includeVector: true,
|
|
169
|
+
});
|
|
159
170
|
|
|
160
171
|
expect(results).toHaveLength(topK);
|
|
161
172
|
expect(results?.[0]?.vector).toEqual(testVectors[0]);
|
|
@@ -165,73 +176,95 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
165
176
|
|
|
166
177
|
describe('Error Handling', () => {
|
|
167
178
|
it('should handle non-existent index queries', async () => {
|
|
168
|
-
await expect(vectorDB.query('non-existent-index-yu', [1, 2, 3])).rejects.toThrow();
|
|
179
|
+
await expect(vectorDB.query({ indexName: 'non-existent-index-yu', queryVector: [1, 2, 3] })).rejects.toThrow();
|
|
169
180
|
});
|
|
170
181
|
|
|
171
182
|
it('should handle invalid dimension vectors', async () => {
|
|
172
183
|
const invalidVector = [1, 2, 3, 4]; // 4D vector for 3D index
|
|
173
|
-
await expect(vectorDB.upsert(testIndexName, [invalidVector])).rejects.toThrow();
|
|
184
|
+
await expect(vectorDB.upsert({ indexName: testIndexName, vectors: [invalidVector] })).rejects.toThrow();
|
|
174
185
|
});
|
|
175
186
|
|
|
176
187
|
it('should handle mismatched metadata and vectors length', async () => {
|
|
177
188
|
const vectors = [[1, 2, 3]];
|
|
178
189
|
const metadata = [{}, {}]; // More metadata than vectors
|
|
179
|
-
await expect(vectorDB.upsert(testIndexName, vectors, metadata)).rejects.toThrow();
|
|
190
|
+
await expect(vectorDB.upsert({ indexName: testIndexName, vectors, metadata })).rejects.toThrow();
|
|
180
191
|
});
|
|
181
192
|
});
|
|
182
193
|
|
|
183
194
|
describe('Filter Validation in Queries', () => {
|
|
184
195
|
it('rejects queries with null values', async () => {
|
|
185
196
|
await expect(
|
|
186
|
-
vectorDB.query(
|
|
187
|
-
|
|
197
|
+
vectorDB.query({
|
|
198
|
+
indexName: testIndexName,
|
|
199
|
+
queryVector: [1, 0, 0],
|
|
200
|
+
filter: {
|
|
201
|
+
field: null,
|
|
202
|
+
},
|
|
188
203
|
}),
|
|
189
204
|
).rejects.toThrow();
|
|
190
205
|
|
|
191
206
|
await expect(
|
|
192
|
-
vectorDB.query(
|
|
193
|
-
|
|
207
|
+
vectorDB.query({
|
|
208
|
+
indexName: testIndexName,
|
|
209
|
+
queryVector: [1, 0, 0],
|
|
210
|
+
filter: {
|
|
211
|
+
other: { $eq: null },
|
|
212
|
+
},
|
|
194
213
|
}),
|
|
195
214
|
).rejects.toThrow();
|
|
196
215
|
});
|
|
197
216
|
|
|
198
217
|
it('validates array operator values', async () => {
|
|
199
218
|
await expect(
|
|
200
|
-
vectorDB.query(
|
|
201
|
-
|
|
219
|
+
vectorDB.query({
|
|
220
|
+
indexName: testIndexName,
|
|
221
|
+
queryVector: [1, 0, 0],
|
|
222
|
+
filter: {
|
|
223
|
+
tags: { $in: null },
|
|
224
|
+
},
|
|
202
225
|
}),
|
|
203
226
|
).rejects.toThrow();
|
|
204
227
|
});
|
|
205
228
|
|
|
206
229
|
it('validates numeric values for comparison operators', async () => {
|
|
207
230
|
await expect(
|
|
208
|
-
vectorDB.query(
|
|
209
|
-
|
|
231
|
+
vectorDB.query({
|
|
232
|
+
indexName: testIndexName,
|
|
233
|
+
queryVector: [1, 0, 0],
|
|
234
|
+
filter: {
|
|
235
|
+
price: { $gt: 'not-a-number' },
|
|
236
|
+
},
|
|
210
237
|
}),
|
|
211
238
|
).rejects.toThrow();
|
|
212
239
|
});
|
|
213
240
|
|
|
214
241
|
it('validates value types', async () => {
|
|
215
242
|
await expect(
|
|
216
|
-
vectorDB.query(
|
|
217
|
-
|
|
243
|
+
vectorDB.query({
|
|
244
|
+
indexName: testIndexName,
|
|
245
|
+
queryVector: [1, 0, 0],
|
|
246
|
+
filter: { date: { $gt: 'not-a-date' } },
|
|
218
247
|
}),
|
|
219
248
|
).rejects.toThrow();
|
|
220
249
|
|
|
221
250
|
await expect(
|
|
222
|
-
vectorDB.query(
|
|
223
|
-
|
|
251
|
+
vectorDB.query({
|
|
252
|
+
indexName: testIndexName,
|
|
253
|
+
queryVector: [1, 0, 0],
|
|
254
|
+
filter: { number: { $lt: 'not-a-number' } },
|
|
224
255
|
}),
|
|
225
256
|
).rejects.toThrow();
|
|
226
257
|
});
|
|
227
258
|
|
|
228
259
|
it('validates array operators', async () => {
|
|
229
|
-
const invalidValues = [123, 'string', true, { key: 'value' }, null
|
|
260
|
+
const invalidValues = [123, 'string', true, { key: 'value' }, null];
|
|
230
261
|
for (const op of ['$in', '$nin']) {
|
|
231
262
|
for (const val of invalidValues) {
|
|
232
263
|
await expect(
|
|
233
|
-
vectorDB.query(
|
|
234
|
-
|
|
264
|
+
vectorDB.query({
|
|
265
|
+
indexName: testIndexName,
|
|
266
|
+
queryVector: [1, 0, 0],
|
|
267
|
+
filter: { field: { [op]: val } },
|
|
235
268
|
}),
|
|
236
269
|
).rejects.toThrow();
|
|
237
270
|
}
|
|
@@ -244,8 +277,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
244
277
|
for (const op of ['$in', '$nin']) {
|
|
245
278
|
for (const val of invalidValues) {
|
|
246
279
|
await expect(
|
|
247
|
-
vectorDB.query(
|
|
248
|
-
|
|
280
|
+
vectorDB.query({
|
|
281
|
+
indexName: testIndexName,
|
|
282
|
+
queryVector: [1, 0, 0],
|
|
283
|
+
filter: { field: { [op]: val } },
|
|
249
284
|
}),
|
|
250
285
|
).rejects.toThrow();
|
|
251
286
|
}
|
|
@@ -256,8 +291,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
256
291
|
// Basic equality can accept any non-undefined value
|
|
257
292
|
for (const op of ['$eq', '$ne']) {
|
|
258
293
|
await expect(
|
|
259
|
-
vectorDB.query(
|
|
260
|
-
|
|
294
|
+
vectorDB.query({
|
|
295
|
+
indexName: testIndexName,
|
|
296
|
+
queryVector: [1, 0, 0],
|
|
297
|
+
filter: { field: { [op]: undefined } },
|
|
261
298
|
}),
|
|
262
299
|
).rejects.toThrow();
|
|
263
300
|
}
|
|
@@ -268,8 +305,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
268
305
|
for (const op of numOps) {
|
|
269
306
|
for (const val of invalidNumericValues) {
|
|
270
307
|
await expect(
|
|
271
|
-
vectorDB.query(
|
|
272
|
-
|
|
308
|
+
vectorDB.query({
|
|
309
|
+
indexName: testIndexName,
|
|
310
|
+
queryVector: [1, 0, 0],
|
|
311
|
+
filter: { field: { [op]: val } },
|
|
273
312
|
}),
|
|
274
313
|
).rejects.toThrow();
|
|
275
314
|
}
|
|
@@ -278,29 +317,45 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
278
317
|
|
|
279
318
|
it('validates multiple invalid values', async () => {
|
|
280
319
|
await expect(
|
|
281
|
-
vectorDB.query(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
320
|
+
vectorDB.query({
|
|
321
|
+
indexName: testIndexName,
|
|
322
|
+
queryVector: [1, 0, 0],
|
|
323
|
+
filter: {
|
|
324
|
+
field1: { $in: 'not-array' },
|
|
325
|
+
field2: { $exists: 'not-boolean' },
|
|
326
|
+
field3: { $gt: 'not-number' },
|
|
327
|
+
},
|
|
285
328
|
}),
|
|
286
329
|
).rejects.toThrow();
|
|
287
330
|
});
|
|
288
331
|
|
|
289
332
|
it('handles empty object filters', async () => {
|
|
290
333
|
// Test empty object at top level
|
|
291
|
-
await expect(
|
|
334
|
+
await expect(
|
|
335
|
+
vectorDB.query({
|
|
336
|
+
indexName: testIndexName,
|
|
337
|
+
queryVector: [1, 0, 0],
|
|
338
|
+
filter: { field: { $eq: {} } },
|
|
339
|
+
}),
|
|
340
|
+
).rejects.toThrow();
|
|
292
341
|
});
|
|
293
342
|
|
|
294
343
|
it('handles empty/undefined filters by returning all results', async () => {
|
|
295
344
|
const noFilterCases = [{ field: {} }, { field: undefined }, { field: { $in: undefined } }];
|
|
296
345
|
|
|
297
346
|
for (const filter of noFilterCases) {
|
|
298
|
-
await expect(vectorDB.query(testIndexName, [1, 0, 0],
|
|
347
|
+
await expect(vectorDB.query({ indexName: testIndexName, queryVector: [1, 0, 0], filter })).rejects.toThrow();
|
|
299
348
|
}
|
|
300
349
|
});
|
|
301
350
|
it('handles empty object filters', async () => {
|
|
302
351
|
// Test empty object at top level
|
|
303
|
-
await expect(
|
|
352
|
+
await expect(
|
|
353
|
+
vectorDB.query({
|
|
354
|
+
indexName: testIndexName,
|
|
355
|
+
queryVector: [1, 0, 0],
|
|
356
|
+
filter: {},
|
|
357
|
+
}),
|
|
358
|
+
).rejects.toThrow();
|
|
304
359
|
});
|
|
305
360
|
});
|
|
306
361
|
|
|
@@ -309,10 +364,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
309
364
|
beforeAll(async () => {
|
|
310
365
|
try {
|
|
311
366
|
await vectorDB.deleteIndex(testIndexName2);
|
|
312
|
-
} catch
|
|
367
|
+
} catch {
|
|
313
368
|
// Ignore errors if index doesn't exist
|
|
314
369
|
}
|
|
315
|
-
await vectorDB.createIndex(testIndexName2, dimension);
|
|
370
|
+
await vectorDB.createIndex({ indexName: testIndexName2, dimension });
|
|
316
371
|
|
|
317
372
|
const vectors = [
|
|
318
373
|
[1, 0, 0], // Electronics
|
|
@@ -348,7 +403,7 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
348
403
|
},
|
|
349
404
|
];
|
|
350
405
|
|
|
351
|
-
await vectorDB.upsert(testIndexName2, vectors, metadata);
|
|
406
|
+
await vectorDB.upsert({ indexName: testIndexName2, vectors, metadata });
|
|
352
407
|
// Wait for indexing
|
|
353
408
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
354
409
|
});
|
|
@@ -357,15 +412,17 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
357
412
|
// Cleanup after tests
|
|
358
413
|
try {
|
|
359
414
|
await vectorDB.deleteIndex(testIndexName2);
|
|
360
|
-
} catch
|
|
415
|
+
} catch {
|
|
361
416
|
// Ignore cleanup errors
|
|
362
417
|
}
|
|
363
418
|
});
|
|
364
419
|
|
|
365
420
|
describe('Basic Comparison Operators', () => {
|
|
366
421
|
it('filters with $eq operator', async () => {
|
|
367
|
-
const results = await vectorDB.query(
|
|
368
|
-
|
|
422
|
+
const results = await vectorDB.query({
|
|
423
|
+
indexName: testIndexName2,
|
|
424
|
+
queryVector: [1, 0, 0],
|
|
425
|
+
filter: { category: { $eq: 'electronics' } },
|
|
369
426
|
});
|
|
370
427
|
expect(results.length).toBe(2);
|
|
371
428
|
results.forEach(result => {
|
|
@@ -374,8 +431,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
374
431
|
});
|
|
375
432
|
|
|
376
433
|
it('filters with implicit $eq', async () => {
|
|
377
|
-
const results = await vectorDB.query(
|
|
378
|
-
|
|
434
|
+
const results = await vectorDB.query({
|
|
435
|
+
indexName: testIndexName2,
|
|
436
|
+
queryVector: [1, 0, 0],
|
|
437
|
+
filter: { category: 'electronics' }, // implicit $eq
|
|
379
438
|
});
|
|
380
439
|
expect(results.length).toBe(2);
|
|
381
440
|
results.forEach(result => {
|
|
@@ -383,8 +442,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
383
442
|
});
|
|
384
443
|
});
|
|
385
444
|
it('filters with $gt operator', async () => {
|
|
386
|
-
const results = await vectorDB.query(
|
|
387
|
-
|
|
445
|
+
const results = await vectorDB.query({
|
|
446
|
+
indexName: testIndexName2,
|
|
447
|
+
queryVector: [1, 0, 0],
|
|
448
|
+
filter: { price: { $gt: 500 } },
|
|
388
449
|
});
|
|
389
450
|
expect(results.length).toBe(1);
|
|
390
451
|
results.forEach(result => {
|
|
@@ -393,8 +454,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
393
454
|
});
|
|
394
455
|
|
|
395
456
|
it('filters with $gte operator', async () => {
|
|
396
|
-
const results = await vectorDB.query(
|
|
397
|
-
|
|
457
|
+
const results = await vectorDB.query({
|
|
458
|
+
indexName: testIndexName2,
|
|
459
|
+
queryVector: [1, 0, 0],
|
|
460
|
+
filter: { price: { $gte: 500 } },
|
|
398
461
|
});
|
|
399
462
|
expect(results.length).toBe(2);
|
|
400
463
|
results.forEach(result => {
|
|
@@ -403,8 +466,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
403
466
|
});
|
|
404
467
|
|
|
405
468
|
it('filters with $lt operator', async () => {
|
|
406
|
-
const results = await vectorDB.query(
|
|
407
|
-
|
|
469
|
+
const results = await vectorDB.query({
|
|
470
|
+
indexName: testIndexName2,
|
|
471
|
+
queryVector: [1, 0, 0],
|
|
472
|
+
filter: { price: { $lt: 100 } },
|
|
408
473
|
});
|
|
409
474
|
expect(results.length).toBe(2);
|
|
410
475
|
results.forEach(result => {
|
|
@@ -413,8 +478,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
413
478
|
});
|
|
414
479
|
|
|
415
480
|
it('filters with $lte operator', async () => {
|
|
416
|
-
const results = await vectorDB.query(
|
|
417
|
-
|
|
481
|
+
const results = await vectorDB.query({
|
|
482
|
+
indexName: testIndexName2,
|
|
483
|
+
queryVector: [1, 0, 0],
|
|
484
|
+
filter: { price: { $lte: 500 } },
|
|
418
485
|
});
|
|
419
486
|
expect(results.length).toBeGreaterThan(0);
|
|
420
487
|
results.forEach(result => {
|
|
@@ -423,8 +490,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
423
490
|
});
|
|
424
491
|
|
|
425
492
|
it('filters with $gte, $lt, $lte operators', async () => {
|
|
426
|
-
const results = await vectorDB.query(
|
|
427
|
-
|
|
493
|
+
const results = await vectorDB.query({
|
|
494
|
+
indexName: testIndexName2,
|
|
495
|
+
queryVector: [1, 0, 0],
|
|
496
|
+
filter: { price: { $gte: 25, $lte: 500 } },
|
|
428
497
|
});
|
|
429
498
|
expect(results.length).toBe(2);
|
|
430
499
|
results.forEach(result => {
|
|
@@ -434,8 +503,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
434
503
|
});
|
|
435
504
|
|
|
436
505
|
it('filters with $ne operator', async () => {
|
|
437
|
-
const results = await vectorDB.query(
|
|
438
|
-
|
|
506
|
+
const results = await vectorDB.query({
|
|
507
|
+
indexName: testIndexName2,
|
|
508
|
+
queryVector: [1, 0, 0],
|
|
509
|
+
filter: { category: { $ne: 'electronics' } },
|
|
439
510
|
});
|
|
440
511
|
expect(results.length).toBe(2);
|
|
441
512
|
results.forEach(result => {
|
|
@@ -444,8 +515,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
444
515
|
});
|
|
445
516
|
|
|
446
517
|
it('filters with boolean values', async () => {
|
|
447
|
-
const results = await vectorDB.query(
|
|
448
|
-
|
|
518
|
+
const results = await vectorDB.query({
|
|
519
|
+
indexName: testIndexName2,
|
|
520
|
+
queryVector: [1, 0, 0],
|
|
521
|
+
filter: { inStock: true }, // test both implicit
|
|
449
522
|
});
|
|
450
523
|
expect(results.length).toBe(3);
|
|
451
524
|
results.forEach(result => {
|
|
@@ -454,9 +527,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
454
527
|
});
|
|
455
528
|
|
|
456
529
|
it('filters with multiple fields', async () => {
|
|
457
|
-
const results = await vectorDB.query(
|
|
458
|
-
|
|
459
|
-
|
|
530
|
+
const results = await vectorDB.query({
|
|
531
|
+
indexName: testIndexName2,
|
|
532
|
+
queryVector: [1, 0, 0],
|
|
533
|
+
filter: { category: 'electronics', price: 1000 },
|
|
460
534
|
});
|
|
461
535
|
expect(results.length).toBeGreaterThan(0);
|
|
462
536
|
results.forEach(result => {
|
|
@@ -467,8 +541,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
467
541
|
|
|
468
542
|
describe('Array Operators', () => {
|
|
469
543
|
it('filters with $in operator', async () => {
|
|
470
|
-
const results = await vectorDB.query(
|
|
471
|
-
|
|
544
|
+
const results = await vectorDB.query({
|
|
545
|
+
indexName: testIndexName2,
|
|
546
|
+
queryVector: [1, 0, 0],
|
|
547
|
+
filter: { category: { $in: ['electronics', 'books'] } },
|
|
472
548
|
});
|
|
473
549
|
expect(results.length).toBeGreaterThan(0);
|
|
474
550
|
results.forEach(result => {
|
|
@@ -477,8 +553,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
477
553
|
});
|
|
478
554
|
|
|
479
555
|
it('should filter with $in operator for numbers', async () => {
|
|
480
|
-
const results = await vectorDB.query(
|
|
481
|
-
|
|
556
|
+
const results = await vectorDB.query({
|
|
557
|
+
indexName: testIndexName2,
|
|
558
|
+
queryVector: [1, 0, 0],
|
|
559
|
+
filter: { price: { $in: [50, 75, 1000] } },
|
|
482
560
|
});
|
|
483
561
|
expect(results.length).toBeGreaterThan(0);
|
|
484
562
|
results.forEach(result => {
|
|
@@ -487,8 +565,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
487
565
|
});
|
|
488
566
|
|
|
489
567
|
it('filters with $in operator for booleans', async () => {
|
|
490
|
-
const results = await vectorDB.query(
|
|
491
|
-
|
|
568
|
+
const results = await vectorDB.query({
|
|
569
|
+
indexName: testIndexName2,
|
|
570
|
+
queryVector: [1, 0, 0],
|
|
571
|
+
filter: { inStock: { $in: [true] } },
|
|
492
572
|
});
|
|
493
573
|
expect(results.length).toBeGreaterThan(0);
|
|
494
574
|
results.forEach(result => {
|
|
@@ -499,8 +579,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
499
579
|
|
|
500
580
|
describe('Logical Operators', () => {
|
|
501
581
|
it('filters with $and operator', async () => {
|
|
502
|
-
const results = await vectorDB.query(
|
|
503
|
-
|
|
582
|
+
const results = await vectorDB.query({
|
|
583
|
+
indexName: testIndexName2,
|
|
584
|
+
queryVector: [1, 0, 0],
|
|
585
|
+
filter: { $and: [{ category: 'electronics' }, { price: { $gt: 500 } }] },
|
|
504
586
|
});
|
|
505
587
|
expect(results.length).toBe(1);
|
|
506
588
|
expect(results[0]?.metadata?.category).toBe('electronics');
|
|
@@ -508,8 +590,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
508
590
|
});
|
|
509
591
|
|
|
510
592
|
it('should filter with $and operator', async () => {
|
|
511
|
-
const results = await vectorDB.query(
|
|
512
|
-
|
|
593
|
+
const results = await vectorDB.query({
|
|
594
|
+
indexName: testIndexName2,
|
|
595
|
+
queryVector: [1, 0, 0],
|
|
596
|
+
filter: { $and: [{ category: 'electronics' }, { price: { $gt: 700 } }, { inStock: true }] },
|
|
513
597
|
});
|
|
514
598
|
expect(results.length).toBeGreaterThan(0);
|
|
515
599
|
results.forEach(result => {
|
|
@@ -520,8 +604,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
520
604
|
});
|
|
521
605
|
|
|
522
606
|
it('filters with $or operator', async () => {
|
|
523
|
-
const results = await vectorDB.query(
|
|
524
|
-
|
|
607
|
+
const results = await vectorDB.query({
|
|
608
|
+
indexName: testIndexName2,
|
|
609
|
+
queryVector: [1, 0, 0],
|
|
610
|
+
filter: { $or: [{ price: { $gt: 900 } }, { rating: { $gt: 4.8 } }] },
|
|
525
611
|
});
|
|
526
612
|
expect(results.length).toBe(2);
|
|
527
613
|
results.forEach(result => {
|
|
@@ -530,8 +616,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
530
616
|
});
|
|
531
617
|
|
|
532
618
|
it('should filter with $or operator', async () => {
|
|
533
|
-
const results = await vectorDB.query(
|
|
534
|
-
|
|
619
|
+
const results = await vectorDB.query({
|
|
620
|
+
indexName: testIndexName2,
|
|
621
|
+
queryVector: [1, 0, 0],
|
|
622
|
+
filter: { $or: [{ price: { $gt: 900 } }, { category: { $in: ['electronics', 'books'] } }] },
|
|
535
623
|
});
|
|
536
624
|
expect(results.length).toBeGreaterThan(0);
|
|
537
625
|
results.forEach(result => {
|
|
@@ -542,14 +630,18 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
542
630
|
});
|
|
543
631
|
|
|
544
632
|
it('should handle nested logical operators', async () => {
|
|
545
|
-
const results = await vectorDB.query(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
633
|
+
const results = await vectorDB.query({
|
|
634
|
+
indexName: testIndexName2,
|
|
635
|
+
queryVector: [1, 0, 0],
|
|
636
|
+
filter: {
|
|
637
|
+
$and: [
|
|
638
|
+
{
|
|
639
|
+
$or: [{ category: 'electronics' }, { category: 'books' }],
|
|
640
|
+
},
|
|
641
|
+
{ price: { $lt: 100 } },
|
|
642
|
+
{ inStock: true },
|
|
643
|
+
],
|
|
644
|
+
},
|
|
553
645
|
});
|
|
554
646
|
expect(results.length).toBeGreaterThan(0);
|
|
555
647
|
results.forEach(result => {
|
|
@@ -560,22 +652,28 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
560
652
|
});
|
|
561
653
|
|
|
562
654
|
it('uses implicit $eq within $or', async () => {
|
|
563
|
-
const results = await vectorDB.query(
|
|
564
|
-
|
|
655
|
+
const results = await vectorDB.query({
|
|
656
|
+
indexName: testIndexName2,
|
|
657
|
+
queryVector: [1, 0, 0],
|
|
658
|
+
filter: { $or: [{ category: 'electronics' }, { price: { $gt: 100 } }] },
|
|
565
659
|
});
|
|
566
660
|
expect(results.length).toBeGreaterThan(0);
|
|
567
661
|
});
|
|
568
662
|
|
|
569
663
|
it('requires multiple conditions in logical operators', async () => {
|
|
570
664
|
await expect(
|
|
571
|
-
vectorDB.query(
|
|
572
|
-
|
|
665
|
+
vectorDB.query({
|
|
666
|
+
indexName: testIndexName2,
|
|
667
|
+
queryVector: [1, 0, 0],
|
|
668
|
+
filter: { $and: [{ category: 'electronics' }] },
|
|
573
669
|
}),
|
|
574
670
|
).rejects.toThrow();
|
|
575
671
|
|
|
576
672
|
await expect(
|
|
577
|
-
vectorDB.query(
|
|
578
|
-
|
|
673
|
+
vectorDB.query({
|
|
674
|
+
indexName: testIndexName2,
|
|
675
|
+
queryVector: [1, 0, 0],
|
|
676
|
+
filter: { $or: [{ price: { $gt: 900 } }] },
|
|
579
677
|
}),
|
|
580
678
|
).rejects.toThrow();
|
|
581
679
|
});
|
|
@@ -583,14 +681,18 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
583
681
|
|
|
584
682
|
describe('Complex Filter Combinations', () => {
|
|
585
683
|
it('combines multiple operators and conditions', async () => {
|
|
586
|
-
const results = await vectorDB.query(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
684
|
+
const results = await vectorDB.query({
|
|
685
|
+
indexName: testIndexName2,
|
|
686
|
+
queryVector: [1, 0, 0],
|
|
687
|
+
filter: {
|
|
688
|
+
$and: [
|
|
689
|
+
{ price: { $gt: 20 } },
|
|
690
|
+
{ inStock: true },
|
|
691
|
+
{
|
|
692
|
+
$or: [{ category: { $in: ['books'] } }, { rating: { $gt: 4.5 } }],
|
|
693
|
+
},
|
|
694
|
+
],
|
|
695
|
+
},
|
|
594
696
|
});
|
|
595
697
|
expect(results.length).toBeGreaterThan(0);
|
|
596
698
|
results.forEach(result => {
|
|
@@ -601,15 +703,19 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
601
703
|
});
|
|
602
704
|
|
|
603
705
|
it('handles complex nested conditions', async () => {
|
|
604
|
-
const results = await vectorDB.query(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
706
|
+
const results = await vectorDB.query({
|
|
707
|
+
indexName: testIndexName2,
|
|
708
|
+
queryVector: [1, 0, 0],
|
|
709
|
+
filter: {
|
|
710
|
+
$or: [
|
|
711
|
+
{
|
|
712
|
+
$and: [{ category: 'electronics' }, { price: { $gt: 700 } }],
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
$and: [{ category: 'books' }, { price: { $lt: 20 } }],
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
},
|
|
613
719
|
});
|
|
614
720
|
expect(results.length).toBeGreaterThan(0);
|
|
615
721
|
results.forEach(result => {
|
|
@@ -622,8 +728,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
622
728
|
});
|
|
623
729
|
|
|
624
730
|
it('should combine comparison and array operators', async () => {
|
|
625
|
-
const results = await vectorDB.query(
|
|
626
|
-
|
|
731
|
+
const results = await vectorDB.query({
|
|
732
|
+
indexName: testIndexName2,
|
|
733
|
+
queryVector: [1, 0, 0],
|
|
734
|
+
filter: { $and: [{ price: { $gte: 500 } }, { rating: { $gt: 4.5 } }] },
|
|
627
735
|
});
|
|
628
736
|
expect(results.length).toBeGreaterThan(0);
|
|
629
737
|
results.forEach(result => {
|
|
@@ -633,8 +741,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
633
741
|
});
|
|
634
742
|
|
|
635
743
|
it('should handle multiple conditions on same field', async () => {
|
|
636
|
-
const results = await vectorDB.query(
|
|
637
|
-
|
|
744
|
+
const results = await vectorDB.query({
|
|
745
|
+
indexName: testIndexName2,
|
|
746
|
+
queryVector: [1, 0, 0],
|
|
747
|
+
filter: { $and: [{ price: { $gte: 30 } }, { price: { $lte: 800 } }] },
|
|
638
748
|
});
|
|
639
749
|
expect(results.length).toBeGreaterThan(0);
|
|
640
750
|
results.forEach(result => {
|
|
@@ -645,15 +755,19 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
645
755
|
});
|
|
646
756
|
|
|
647
757
|
it('should handle deeply nested logical operators', async () => {
|
|
648
|
-
const results = await vectorDB.query(
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
758
|
+
const results = await vectorDB.query({
|
|
759
|
+
indexName: testIndexName2,
|
|
760
|
+
queryVector: [1, 0, 0],
|
|
761
|
+
filter: {
|
|
762
|
+
$or: [
|
|
763
|
+
{
|
|
764
|
+
$and: [{ category: 'electronics' }, { price: { $gt: 700 } }, { rating: { $gt: 4.5 } }],
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
$and: [{ category: 'books' }, { price: { $lt: 50 } }, { rating: { $gt: 4.0 } }],
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
},
|
|
657
771
|
});
|
|
658
772
|
expect(results.length).toBeGreaterThan(0);
|
|
659
773
|
results.forEach(result => {
|
|
@@ -701,13 +815,19 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
701
815
|
},
|
|
702
816
|
];
|
|
703
817
|
|
|
704
|
-
await vectorDB.upsert(
|
|
818
|
+
await vectorDB.upsert({
|
|
819
|
+
indexName: testIndexName2,
|
|
820
|
+
vectors,
|
|
821
|
+
metadata,
|
|
822
|
+
});
|
|
705
823
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
706
824
|
});
|
|
707
825
|
|
|
708
826
|
it('handles special numeric values', async () => {
|
|
709
|
-
const results = await vectorDB.query(
|
|
710
|
-
|
|
827
|
+
const results = await vectorDB.query({
|
|
828
|
+
indexName: testIndexName2,
|
|
829
|
+
queryVector: [1, 0, 0],
|
|
830
|
+
filter: { $or: [{ zero: 0 }, { negativeZero: 0 }] },
|
|
711
831
|
});
|
|
712
832
|
expect(results.length).toBeGreaterThan(0);
|
|
713
833
|
results.forEach(result => {
|
|
@@ -717,15 +837,23 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
717
837
|
});
|
|
718
838
|
|
|
719
839
|
it('handles extreme numeric values', async () => {
|
|
720
|
-
const results = await vectorDB.query(
|
|
721
|
-
|
|
840
|
+
const results = await vectorDB.query({
|
|
841
|
+
indexName: testIndexName2,
|
|
842
|
+
queryVector: [1, 0, 0],
|
|
843
|
+
filter: {
|
|
844
|
+
$or: [{ maxInt: { $gte: Number.MAX_SAFE_INTEGER } }, { minInt: { $lte: Number.MIN_SAFE_INTEGER } }],
|
|
845
|
+
},
|
|
722
846
|
});
|
|
723
847
|
expect(results.length).toBe(1);
|
|
724
848
|
});
|
|
725
849
|
|
|
726
850
|
it('should handle numeric comparisons with decimals', async () => {
|
|
727
|
-
const results = await vectorDB.query(
|
|
728
|
-
|
|
851
|
+
const results = await vectorDB.query({
|
|
852
|
+
indexName: testIndexName2,
|
|
853
|
+
queryVector: [1, 0, 0],
|
|
854
|
+
filter: {
|
|
855
|
+
rating: { $gt: 4.5 },
|
|
856
|
+
},
|
|
729
857
|
});
|
|
730
858
|
expect(results.length).toBeGreaterThan(0);
|
|
731
859
|
results.forEach(result => {
|
|
@@ -734,8 +862,10 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
734
862
|
});
|
|
735
863
|
|
|
736
864
|
it('should handle boolean values', async () => {
|
|
737
|
-
const results = await vectorDB.query(
|
|
738
|
-
|
|
865
|
+
const results = await vectorDB.query({
|
|
866
|
+
indexName: testIndexName2,
|
|
867
|
+
queryVector: [1, 0, 0],
|
|
868
|
+
filter: { inStock: { $eq: false } },
|
|
739
869
|
});
|
|
740
870
|
expect(results.length).toBeGreaterThan(0);
|
|
741
871
|
results.forEach(result => {
|
|
@@ -747,46 +877,78 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
747
877
|
describe('Additional Validation Tests', () => {
|
|
748
878
|
it('should throw error as date is not supported', async () => {
|
|
749
879
|
await expect(
|
|
750
|
-
vectorDB.query(
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
880
|
+
vectorDB.query({
|
|
881
|
+
indexName: testIndexName2,
|
|
882
|
+
queryVector: [1, 0, 0],
|
|
883
|
+
filter: {
|
|
884
|
+
$and: [
|
|
885
|
+
{ currentDate: { $lte: new Date().toISOString() } },
|
|
886
|
+
{ currentDate: { $gt: new Date(0).toISOString() } },
|
|
887
|
+
],
|
|
888
|
+
},
|
|
755
889
|
}),
|
|
756
890
|
).rejects.toThrow();
|
|
757
891
|
});
|
|
758
892
|
it('should throw error as empty array in $in operator is not supported', async () => {
|
|
759
893
|
await expect(
|
|
760
|
-
vectorDB.query(
|
|
761
|
-
|
|
894
|
+
vectorDB.query({
|
|
895
|
+
indexName: testIndexName2,
|
|
896
|
+
queryVector: [1, 0, 0],
|
|
897
|
+
filter: {
|
|
898
|
+
category: { $in: [] },
|
|
899
|
+
},
|
|
762
900
|
}),
|
|
763
901
|
).rejects.toThrow();
|
|
764
902
|
});
|
|
765
903
|
it('should reject non-numeric values in numeric comparisons', async () => {
|
|
766
904
|
await expect(
|
|
767
|
-
vectorDB.query(
|
|
768
|
-
|
|
905
|
+
vectorDB.query({
|
|
906
|
+
indexName: testIndexName2,
|
|
907
|
+
queryVector: [1, 0, 0],
|
|
908
|
+
filter: {
|
|
909
|
+
price: { $gt: '500' }, // string instead of number
|
|
910
|
+
},
|
|
769
911
|
}),
|
|
770
912
|
).rejects.toThrow();
|
|
771
913
|
});
|
|
772
914
|
|
|
773
915
|
it('should reject mixed types in $in operator', async () => {
|
|
774
916
|
await expect(
|
|
775
|
-
vectorDB.query(
|
|
776
|
-
|
|
917
|
+
vectorDB.query({
|
|
918
|
+
indexName: testIndexName2,
|
|
919
|
+
queryVector: [1, 0, 0],
|
|
920
|
+
filter: {
|
|
921
|
+
field: { $in: ['string', 123] }, // mixed string and number
|
|
922
|
+
},
|
|
777
923
|
}),
|
|
778
924
|
).rejects.toThrow();
|
|
779
925
|
});
|
|
780
926
|
it('should handle undefined filter', async () => {
|
|
781
|
-
const results1 = await vectorDB.query(
|
|
782
|
-
|
|
927
|
+
const results1 = await vectorDB.query({
|
|
928
|
+
indexName: testIndexName2,
|
|
929
|
+
queryVector: [1, 0, 0],
|
|
930
|
+
filter: undefined,
|
|
931
|
+
});
|
|
932
|
+
const results2 = await vectorDB.query({
|
|
933
|
+
indexName: testIndexName2,
|
|
934
|
+
queryVector: [1, 0, 0],
|
|
935
|
+
filter: undefined,
|
|
936
|
+
});
|
|
783
937
|
expect(results1).toEqual(results2);
|
|
784
938
|
expect(results1.length).toBeGreaterThan(0);
|
|
785
939
|
});
|
|
786
940
|
|
|
787
941
|
it('should handle null filter', async () => {
|
|
788
|
-
const results = await vectorDB.query(
|
|
789
|
-
|
|
942
|
+
const results = await vectorDB.query({
|
|
943
|
+
indexName: testIndexName2,
|
|
944
|
+
queryVector: [1, 0, 0],
|
|
945
|
+
filter: null,
|
|
946
|
+
});
|
|
947
|
+
const results2 = await vectorDB.query({
|
|
948
|
+
indexName: testIndexName2,
|
|
949
|
+
queryVector: [1, 0, 0],
|
|
950
|
+
filter: undefined,
|
|
951
|
+
});
|
|
790
952
|
expect(results).toEqual(results2);
|
|
791
953
|
expect(results.length).toBeGreaterThan(0);
|
|
792
954
|
});
|
|
@@ -794,8 +956,12 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
794
956
|
|
|
795
957
|
describe('Additional Edge Cases', () => {
|
|
796
958
|
it('should handle exact boundary conditions', async () => {
|
|
797
|
-
const results = await vectorDB.query(
|
|
798
|
-
|
|
959
|
+
const results = await vectorDB.query({
|
|
960
|
+
indexName: testIndexName2,
|
|
961
|
+
queryVector: [1, 0, 0],
|
|
962
|
+
filter: {
|
|
963
|
+
$and: [{ price: { $gte: 25 } }, { price: { $lte: 1000 } }],
|
|
964
|
+
},
|
|
799
965
|
});
|
|
800
966
|
expect(results.length).toBeGreaterThan(0);
|
|
801
967
|
expect(results.some(r => r.metadata?.price === 25)).toBe(true);
|
|
@@ -805,15 +971,19 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
805
971
|
|
|
806
972
|
describe('Additional Complex Logical Combinations', () => {
|
|
807
973
|
it('should handle deeply nested $or conditions', async () => {
|
|
808
|
-
const results = await vectorDB.query(
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
974
|
+
const results = await vectorDB.query({
|
|
975
|
+
indexName: testIndexName2,
|
|
976
|
+
queryVector: [1, 0, 0],
|
|
977
|
+
filter: {
|
|
978
|
+
$or: [
|
|
979
|
+
{
|
|
980
|
+
$and: [{ category: 'electronics' }, { $or: [{ price: { $gt: 900 } }, { rating: { $gt: 4.8 } }] }],
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
$and: [{ category: 'books' }, { $or: [{ price: { $lt: 30 } }, { rating: { $gt: 4.5 } }] }],
|
|
984
|
+
},
|
|
985
|
+
],
|
|
986
|
+
},
|
|
817
987
|
});
|
|
818
988
|
expect(results.length).toBeGreaterThan(0);
|
|
819
989
|
results.forEach(result => {
|
|
@@ -826,8 +996,12 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
826
996
|
});
|
|
827
997
|
|
|
828
998
|
it('should handle multiple field comparisons with same value', async () => {
|
|
829
|
-
const results = await vectorDB.query(
|
|
830
|
-
|
|
999
|
+
const results = await vectorDB.query({
|
|
1000
|
+
indexName: testIndexName2,
|
|
1001
|
+
queryVector: [1, 0, 0],
|
|
1002
|
+
filter: {
|
|
1003
|
+
$or: [{ price: { $gt: 500 } }, { rating: { $gt: 4.5 } }],
|
|
1004
|
+
},
|
|
831
1005
|
});
|
|
832
1006
|
expect(results.length).toBeGreaterThan(0);
|
|
833
1007
|
results.forEach(result => {
|
|
@@ -838,12 +1012,16 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
838
1012
|
|
|
839
1013
|
describe('Performance Edge Cases', () => {
|
|
840
1014
|
it('should handle filters with many conditions', async () => {
|
|
841
|
-
const results = await vectorDB.query(
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1015
|
+
const results = await vectorDB.query({
|
|
1016
|
+
indexName: testIndexName2,
|
|
1017
|
+
queryVector: [1, 0, 0],
|
|
1018
|
+
filter: {
|
|
1019
|
+
$and: Array(10)
|
|
1020
|
+
.fill(null)
|
|
1021
|
+
.map(() => ({
|
|
1022
|
+
$or: [{ price: { $gt: 100 } }, { rating: { $gt: 4.0 } }],
|
|
1023
|
+
})),
|
|
1024
|
+
},
|
|
847
1025
|
});
|
|
848
1026
|
expect(results.length).toBeGreaterThan(0);
|
|
849
1027
|
results.forEach(result => {
|
|
@@ -852,12 +1030,20 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
852
1030
|
});
|
|
853
1031
|
|
|
854
1032
|
it('should handle deeply nested conditions efficiently', async () => {
|
|
855
|
-
const results = await vectorDB.query(
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1033
|
+
const results = await vectorDB.query({
|
|
1034
|
+
indexName: testIndexName2,
|
|
1035
|
+
queryVector: [1, 0, 0],
|
|
1036
|
+
filter: {
|
|
1037
|
+
$or: Array(5)
|
|
1038
|
+
.fill(null)
|
|
1039
|
+
.map(() => ({
|
|
1040
|
+
$and: [
|
|
1041
|
+
{ category: { $in: ['electronics', 'books'] } },
|
|
1042
|
+
{ price: { $gt: 50 } },
|
|
1043
|
+
{ rating: { $gt: 4.0 } },
|
|
1044
|
+
],
|
|
1045
|
+
})),
|
|
1046
|
+
},
|
|
861
1047
|
});
|
|
862
1048
|
expect(results.length).toBeGreaterThan(0);
|
|
863
1049
|
results.forEach(result => {
|
|
@@ -868,22 +1054,331 @@ describe('ChromaVector Integration Tests', () => {
|
|
|
868
1054
|
});
|
|
869
1055
|
|
|
870
1056
|
it('should handle large number of $or conditions', async () => {
|
|
871
|
-
const results = await vectorDB.query(
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1057
|
+
const results = await vectorDB.query({
|
|
1058
|
+
indexName: testIndexName2,
|
|
1059
|
+
queryVector: [1, 0, 0],
|
|
1060
|
+
filter: {
|
|
1061
|
+
$or: [
|
|
1062
|
+
...Array(5)
|
|
1063
|
+
.fill(null)
|
|
1064
|
+
.map((_, i) => ({
|
|
1065
|
+
price: { $gt: i * 100 },
|
|
1066
|
+
})),
|
|
1067
|
+
...Array(5)
|
|
1068
|
+
.fill(null)
|
|
1069
|
+
.map((_, i) => ({
|
|
1070
|
+
rating: { $gt: 4.0 + i * 0.1 },
|
|
1071
|
+
})),
|
|
1072
|
+
],
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
expect(results.length).toBeGreaterThan(0);
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
describe('Document Operations and Filtering', () => {
|
|
1081
|
+
const testDocuments = [
|
|
1082
|
+
'The quick brown fox jumps over the lazy dog',
|
|
1083
|
+
'Pack my box with five dozen liquor jugs',
|
|
1084
|
+
'How vexingly quick daft zebras JUMP',
|
|
1085
|
+
];
|
|
1086
|
+
|
|
1087
|
+
beforeAll(async () => {
|
|
1088
|
+
try {
|
|
1089
|
+
await vectorDB.deleteIndex(testIndexName3);
|
|
1090
|
+
} catch {
|
|
1091
|
+
// Ignore errors if index doesn't exist
|
|
1092
|
+
}
|
|
1093
|
+
await vectorDB.createIndex({ indexName: testIndexName3, dimension });
|
|
1094
|
+
|
|
1095
|
+
const testVectors = [
|
|
1096
|
+
[1.0, 0.0, 0.0],
|
|
1097
|
+
[0.0, 1.0, 0.0],
|
|
1098
|
+
[0.0, 0.0, 1.0],
|
|
1099
|
+
];
|
|
1100
|
+
|
|
1101
|
+
const testMetadata = [
|
|
1102
|
+
{ source: 'pangram1', length: 43 },
|
|
1103
|
+
{ source: 'pangram2', length: 32 },
|
|
1104
|
+
{ source: 'pangram3', length: 30 },
|
|
1105
|
+
];
|
|
1106
|
+
const testIds = ['doc1', 'doc2', 'doc3'];
|
|
1107
|
+
|
|
1108
|
+
await vectorDB.upsert({
|
|
1109
|
+
indexName: testIndexName3,
|
|
1110
|
+
vectors: testVectors,
|
|
1111
|
+
documents: testDocuments,
|
|
1112
|
+
metadata: testMetadata,
|
|
1113
|
+
ids: testIds,
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
// Wait for indexing
|
|
1117
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
afterAll(async () => {
|
|
1121
|
+
// Cleanup after tests
|
|
1122
|
+
try {
|
|
1123
|
+
await vectorDB.deleteIndex(testIndexName3);
|
|
1124
|
+
} catch {
|
|
1125
|
+
// Ignore cleanup errors
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
describe('Basic Document Operations', () => {
|
|
1130
|
+
it('should store and retrieve documents', async () => {
|
|
1131
|
+
const results = await vectorDB.query({ indexName: testIndexName3, queryVector: [1.0, 0.0, 0.0], topK: 3 });
|
|
1132
|
+
expect(results).toHaveLength(3);
|
|
1133
|
+
// Verify documents are returned
|
|
1134
|
+
expect(results[0].document).toBe(testDocuments[0]);
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
it('should filter documents using $contains', async () => {
|
|
1138
|
+
const results = await vectorDB.query({
|
|
1139
|
+
indexName: testIndexName3,
|
|
1140
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1141
|
+
topK: 3,
|
|
1142
|
+
documentFilter: { $contains: 'quick' },
|
|
1143
|
+
});
|
|
1144
|
+
expect(results).toHaveLength(2);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
it('should filter with $not_contains', async () => {
|
|
1148
|
+
const results = await vectorDB.query({
|
|
1149
|
+
indexName: testIndexName3,
|
|
1150
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1151
|
+
topK: 3,
|
|
1152
|
+
documentFilter: { $not_contains: 'fox' },
|
|
1153
|
+
});
|
|
1154
|
+
expect(results.every(r => !r.document?.includes('fox'))).toBe(true);
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it('should combine metadata and document filters', async () => {
|
|
1158
|
+
const results = await vectorDB.query({
|
|
1159
|
+
indexName: testIndexName3,
|
|
1160
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1161
|
+
topK: 3,
|
|
1162
|
+
filter: { source: 'pangram1' },
|
|
1163
|
+
documentFilter: { $contains: 'fox' },
|
|
1164
|
+
});
|
|
1165
|
+
expect(results).toHaveLength(1);
|
|
1166
|
+
expect(results[0].metadata?.source).toBe('pangram1');
|
|
1167
|
+
expect(results[0].document).toContain('fox');
|
|
1168
|
+
});
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
describe('Complex Document Filtering', () => {
|
|
1172
|
+
it('should handle $and conditions', async () => {
|
|
1173
|
+
const results = await vectorDB.query({
|
|
1174
|
+
indexName: testIndexName3,
|
|
1175
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1176
|
+
topK: 3,
|
|
1177
|
+
documentFilter: { $and: [{ $contains: 'quick' }, { $not_contains: 'fox' }] },
|
|
1178
|
+
});
|
|
1179
|
+
expect(results).toHaveLength(1);
|
|
1180
|
+
expect(results[0].document).toContain('quick');
|
|
1181
|
+
expect(results[0].document).not.toContain('fox');
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it('should handle $or conditions', async () => {
|
|
1185
|
+
const results = await vectorDB.query({
|
|
1186
|
+
indexName: testIndexName3,
|
|
1187
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1188
|
+
topK: 3,
|
|
1189
|
+
documentFilter: { $or: [{ $contains: 'fox' }, { $contains: 'zebras' }] },
|
|
1190
|
+
});
|
|
1191
|
+
expect(results).toHaveLength(2);
|
|
1192
|
+
expect(results[0].document).toContain('fox');
|
|
1193
|
+
expect(results[1].document).toContain('zebras');
|
|
1194
|
+
});
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
describe('Edge Cases and Validation', () => {
|
|
1198
|
+
it('should reject empty string in $contains', async () => {
|
|
1199
|
+
await expect(
|
|
1200
|
+
vectorDB.query({
|
|
1201
|
+
indexName: testIndexName3,
|
|
1202
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1203
|
+
topK: 3,
|
|
1204
|
+
documentFilter: { $contains: '' },
|
|
1205
|
+
}),
|
|
1206
|
+
).rejects.toThrow('Expected where document operand value for operator $contains to be a non-empty str');
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
it('should be case sensitive', async () => {
|
|
1210
|
+
// First verify lowercase works
|
|
1211
|
+
const lowerResults = await vectorDB.query({
|
|
1212
|
+
indexName: testIndexName3,
|
|
1213
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1214
|
+
topK: 3,
|
|
1215
|
+
documentFilter: { $contains: 'quick' },
|
|
1216
|
+
});
|
|
1217
|
+
expect(lowerResults.length).toBe(2);
|
|
1218
|
+
|
|
1219
|
+
// Then verify uppercase doesn't match
|
|
1220
|
+
const upperResults = await vectorDB.query({
|
|
1221
|
+
indexName: testIndexName3,
|
|
1222
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1223
|
+
topK: 3,
|
|
1224
|
+
documentFilter: { $contains: 'QUICK' },
|
|
1225
|
+
});
|
|
1226
|
+
expect(upperResults.length).toBe(0);
|
|
1227
|
+
|
|
1228
|
+
const upperResults2 = await vectorDB.query({
|
|
1229
|
+
indexName: testIndexName3,
|
|
1230
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1231
|
+
topK: 3,
|
|
1232
|
+
documentFilter: { $contains: 'JUMP' },
|
|
1233
|
+
});
|
|
1234
|
+
expect(upperResults2.length).toBe(1);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
it('should handle exact string matches', async () => {
|
|
1238
|
+
const results = await vectorDB.query({
|
|
1239
|
+
indexName: testIndexName3,
|
|
1240
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1241
|
+
topK: 3,
|
|
1242
|
+
documentFilter: { $contains: 'quick brown' }, // Test multi-word match
|
|
1243
|
+
});
|
|
1244
|
+
expect(results.length).toBe(1);
|
|
1245
|
+
expect(results[0].document).toContain('quick brown');
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
it('should handle deeply nested logical operators', async () => {
|
|
1249
|
+
const results = await vectorDB.query({
|
|
1250
|
+
indexName: testIndexName3,
|
|
1251
|
+
queryVector: [1.0, 0.0, 0.0],
|
|
1252
|
+
topK: 3,
|
|
1253
|
+
documentFilter: {
|
|
1254
|
+
$or: [
|
|
1255
|
+
{
|
|
1256
|
+
$and: [{ $contains: 'quick' }, { $not_contains: 'fox' }],
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
$and: [{ $contains: 'box' }, { $not_contains: 'quick' }],
|
|
1260
|
+
},
|
|
1261
|
+
],
|
|
1262
|
+
},
|
|
884
1263
|
});
|
|
885
1264
|
expect(results.length).toBeGreaterThan(0);
|
|
1265
|
+
results.forEach(result => {
|
|
1266
|
+
if (result.document?.includes('quick')) {
|
|
1267
|
+
expect(result.document).not.toContain('fox');
|
|
1268
|
+
} else if (result.document?.includes('box')) {
|
|
1269
|
+
expect(result.document).not.toContain('quick');
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
1275
|
+
describe('Deprecation Warnings', () => {
|
|
1276
|
+
const indexName = 'testdeprecationwarnings';
|
|
1277
|
+
|
|
1278
|
+
const indexName2 = 'testdeprecationwarnings2';
|
|
1279
|
+
|
|
1280
|
+
let warnSpy;
|
|
1281
|
+
|
|
1282
|
+
beforeAll(async () => {
|
|
1283
|
+
await vectorDB.createIndex({ indexName: indexName, dimension: 3 });
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
afterAll(async () => {
|
|
1287
|
+
try {
|
|
1288
|
+
await vectorDB.deleteIndex(indexName);
|
|
1289
|
+
} catch {
|
|
1290
|
+
// Ignore errors if index doesn't exist
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
await vectorDB.deleteIndex(indexName2);
|
|
1294
|
+
} catch {
|
|
1295
|
+
// Ignore errors if index doesn't exist
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
beforeEach(async () => {
|
|
1300
|
+
warnSpy = vi.spyOn(vectorDB['logger'], 'warn');
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
afterEach(async () => {
|
|
1304
|
+
warnSpy.mockRestore();
|
|
1305
|
+
try {
|
|
1306
|
+
await vectorDB.deleteIndex(indexName2);
|
|
1307
|
+
} catch {
|
|
1308
|
+
// Ignore errors if index doesn't exist
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
it('should show deprecation warning when using individual args for createIndex', async () => {
|
|
1313
|
+
await vectorDB.createIndex(indexName2, 3, 'cosine');
|
|
1314
|
+
|
|
1315
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1316
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to createIndex() is deprecated'),
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
it('should show deprecation warning when using individual args for upsert', async () => {
|
|
1321
|
+
await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
|
|
1322
|
+
|
|
1323
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1324
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to upsert() is deprecated'),
|
|
1325
|
+
);
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
it('should show deprecation warning when using individual args for query', async () => {
|
|
1329
|
+
await vectorDB.query(indexName, [1, 2, 3], 5);
|
|
1330
|
+
|
|
1331
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1332
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to query() is deprecated'),
|
|
1333
|
+
);
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
it('should not show deprecation warning when using object param for query', async () => {
|
|
1337
|
+
await vectorDB.query({
|
|
1338
|
+
indexName,
|
|
1339
|
+
queryVector: [1, 2, 3],
|
|
1340
|
+
topK: 5,
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
it('should not show deprecation warning when using object param for createIndex', async () => {
|
|
1347
|
+
await vectorDB.createIndex({
|
|
1348
|
+
indexName: indexName2,
|
|
1349
|
+
dimension: 3,
|
|
1350
|
+
metric: 'cosine',
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1356
|
+
it('should not show deprecation warning when using object param for upsert', async () => {
|
|
1357
|
+
await vectorDB.upsert({
|
|
1358
|
+
indexName,
|
|
1359
|
+
vectors: [[1, 2, 3]],
|
|
1360
|
+
metadata: [{ test: 'data' }],
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
it('should maintain backward compatibility with individual args', async () => {
|
|
1367
|
+
// Query
|
|
1368
|
+
const queryResults = await vectorDB.query(indexName, [1, 2, 3], 5);
|
|
1369
|
+
expect(Array.isArray(queryResults)).toBe(true);
|
|
1370
|
+
|
|
1371
|
+
// CreateIndex
|
|
1372
|
+
await expect(vectorDB.createIndex(indexName2, 3, 'cosine')).resolves.not.toThrow();
|
|
1373
|
+
|
|
1374
|
+
// Upsert
|
|
1375
|
+
const upsertResults = await vectorDB.upsert({
|
|
1376
|
+
indexName,
|
|
1377
|
+
vectors: [[1, 2, 3]],
|
|
1378
|
+
metadata: [{ test: 'data' }],
|
|
886
1379
|
});
|
|
1380
|
+
expect(Array.isArray(upsertResults)).toBe(true);
|
|
1381
|
+
expect(upsertResults).toHaveLength(1);
|
|
887
1382
|
});
|
|
888
1383
|
});
|
|
889
1384
|
});
|