@mastra/vectorize 0.1.8-alpha.9 → 0.1.9-alpha.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.
@@ -1,931 +0,0 @@
1
- import { randomUUID } from 'crypto';
2
- import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, afterEach } from 'vitest';
3
-
4
- import { CloudflareVector } from './';
5
-
6
- vi.setConfig({ testTimeout: 80_000, hookTimeout: 80_000 });
7
-
8
- function waitUntilReady(vector: CloudflareVector, indexName: string) {
9
- return new Promise(resolve => {
10
- const interval = setInterval(async () => {
11
- try {
12
- const stats = await vector.describeIndex(indexName);
13
- if (!!stats) {
14
- clearInterval(interval);
15
- resolve(true);
16
- }
17
- } catch (error) {
18
- console.log(error);
19
- }
20
- }, 5000);
21
- });
22
- }
23
-
24
- function waitUntilVectorsIndexed(vector: CloudflareVector, indexName: string, expectedCount: number) {
25
- return new Promise((resolve, reject) => {
26
- const maxAttempts = 30;
27
- let attempts = 0;
28
- const interval = setInterval(async () => {
29
- try {
30
- const stats = await vector.describeIndex(indexName);
31
- if (stats && stats.count >= expectedCount) {
32
- clearInterval(interval);
33
- resolve(true);
34
- }
35
- attempts++;
36
- if (attempts >= maxAttempts) {
37
- clearInterval(interval);
38
- reject(new Error('Timeout waiting for vectors to be indexed'));
39
- }
40
- } catch (error) {
41
- console.log(error);
42
- }
43
- }, 5000);
44
- });
45
- }
46
-
47
- function waitForMetadataIndexes(vector: CloudflareVector, indexName: string, expectedCount: number) {
48
- return new Promise((resolve, reject) => {
49
- const maxAttempts = 30;
50
- let attempts = 0;
51
- const interval = setInterval(async () => {
52
- try {
53
- const indexes = await vector.listMetadataIndexes(indexName);
54
- if (indexes && indexes.length === expectedCount) {
55
- clearInterval(interval);
56
- resolve(true);
57
- }
58
- attempts++;
59
- if (attempts >= maxAttempts) {
60
- clearInterval(interval);
61
- reject(new Error('Timeout waiting for metadata indexes to be created'));
62
- }
63
- } catch (error) {
64
- console.log(error);
65
- }
66
- }, 5000);
67
- });
68
- }
69
-
70
- describe('CloudflareVector', () => {
71
- let vectorDB: CloudflareVector;
72
- const VECTOR_DIMENSION = 1536;
73
- const testIndexName = `default-${randomUUID()}`;
74
- const testIndexName2 = `default-${randomUUID()}`;
75
-
76
- // Helper function to create a normalized vector
77
- const createVector = (primaryDimension: number, value: number = 1.0): number[] => {
78
- const vector = new Array(VECTOR_DIMENSION).fill(0);
79
- vector[primaryDimension] = value;
80
- // Normalize the vector for cosine similarity
81
- const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
82
- return vector.map(val => val / magnitude);
83
- };
84
-
85
- beforeAll(() => {
86
- // Load from environment variables for CI/CD
87
- const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
88
- const apiToken = process.env.CLOUDFLARE_API_TOKEN;
89
-
90
- if (!accountId || !apiToken) {
91
- throw new Error(
92
- 'Missing required environment variables: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, CLOUDFLARE_VECTORIZE_ID',
93
- );
94
- }
95
-
96
- vectorDB = new CloudflareVector({ accountId, apiToken });
97
- });
98
-
99
- afterAll(async () => {
100
- try {
101
- await vectorDB.deleteIndex(testIndexName);
102
- } catch (error) {
103
- console.warn('Failed to delete test index:', error);
104
- }
105
- });
106
-
107
- describe('Index Operations', () => {
108
- const tempIndexName = 'test_temp_index';
109
-
110
- it('should create and list indexes', async () => {
111
- await vectorDB.createIndex({ indexName: tempIndexName, dimension: VECTOR_DIMENSION, metric: 'cosine' });
112
- await waitUntilReady(vectorDB, tempIndexName);
113
- const indexes = await vectorDB.listIndexes();
114
- expect(indexes).toContain(tempIndexName);
115
- });
116
-
117
- it('should describe an index correctly', async () => {
118
- const stats = await vectorDB.describeIndex(tempIndexName);
119
- expect(stats).toEqual({
120
- dimension: VECTOR_DIMENSION,
121
- metric: 'cosine',
122
- count: 0,
123
- });
124
- });
125
-
126
- it('should delete an index', async () => {
127
- await vectorDB.deleteIndex(tempIndexName);
128
- const indexes = await vectorDB.listIndexes();
129
- expect(indexes).not.toContain(tempIndexName);
130
- });
131
- }, 30000);
132
-
133
- describe('Vector Operations', () => {
134
- let vectorIds: string[];
135
- it('should create index before operations', async () => {
136
- await vectorDB.createIndex({ indexName: testIndexName, dimension: VECTOR_DIMENSION, metric: 'cosine' });
137
- await waitUntilReady(vectorDB, testIndexName);
138
- const indexes = await vectorDB.listIndexes();
139
- expect(indexes).toContain(testIndexName);
140
- });
141
-
142
- it('should insert vectors and query them', async () => {
143
- const testVectors = [createVector(0, 1.0), createVector(1, 1.0), createVector(2, 1.0)];
144
-
145
- const testMetadata = [{ label: 'first-dimension' }, { label: 'second-dimension' }, { label: 'third-dimension' }];
146
-
147
- vectorIds = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata });
148
- expect(vectorIds).toHaveLength(3);
149
-
150
- await waitUntilVectorsIndexed(vectorDB, testIndexName, 3);
151
- const stats = await vectorDB.describeIndex(testIndexName);
152
- expect(stats.count).toBeGreaterThan(0);
153
-
154
- const results = await vectorDB.query({ indexName: testIndexName, queryVector: createVector(0, 0.9), topK: 3 });
155
- expect(results).toHaveLength(3);
156
-
157
- if (results.length > 0) {
158
- expect(results[0].metadata).toEqual({ label: 'first-dimension' });
159
- }
160
- }, 30000);
161
-
162
- it('should query vectors and return vector in results', async () => {
163
- const results = await vectorDB.query({
164
- indexName: testIndexName,
165
- queryVector: createVector(0, 0.9),
166
- topK: 3,
167
- includeVector: true,
168
- });
169
-
170
- expect(results).toHaveLength(3);
171
-
172
- for (const result of results) {
173
- expect(result.vector).toBeDefined();
174
- expect(result.vector).toHaveLength(VECTOR_DIMENSION);
175
- }
176
- });
177
- }, 60000);
178
-
179
- describe('Error Handling', () => {
180
- it('should handle invalid dimension vectors', async () => {
181
- await expect(vectorDB.upsert({ indexName: testIndexName, vectors: [[1.0, 0.0]] })).rejects.toThrow();
182
- });
183
-
184
- it('should handle querying with wrong dimensions', async () => {
185
- await expect(vectorDB.query({ indexName: testIndexName, queryVector: [1.0, 0.0] })).rejects.toThrow();
186
- });
187
-
188
- it('should handle non-existent index operations', async () => {
189
- const nonExistentIndex = 'non_existent_index';
190
- await expect(
191
- vectorDB.query({ indexName: nonExistentIndex, queryVector: createVector(0, 1.0) }),
192
- ).rejects.toThrow();
193
- });
194
-
195
- it('rejects queries with filter keys longer than 512 characters', async () => {
196
- const longKey = 'a'.repeat(513);
197
- const filter = { [longKey]: 'value' };
198
-
199
- await expect(
200
- vectorDB.query({ indexName: testIndexName, queryVector: createVector(0, 0.9), topK: 10, filter }),
201
- ).rejects.toThrow();
202
- });
203
-
204
- it('rejects queries with filter keys containing invalid characters', async () => {
205
- const invalidFilters = [
206
- { 'field"name': 'value' }, // Contains "
207
- { $field: 'value' }, // Contains $
208
- { '': 'value' }, // Empty key
209
- ];
210
-
211
- for (const filter of invalidFilters) {
212
- await expect(
213
- vectorDB.query({ indexName: testIndexName, queryVector: createVector(0, 0.9), topK: 10, filter }),
214
- ).rejects.toThrow();
215
- }
216
- });
217
-
218
- it('allows queries with valid range operator combinations', async () => {
219
- const validFilters = [
220
- { field: { $gt: 5, $lt: 10 } },
221
- { field: { $gte: 0, $lte: 100 } },
222
- { field: { $gt: 5, $lte: 10 } },
223
- ];
224
-
225
- for (const filter of validFilters) {
226
- await expect(
227
- vectorDB.query({ indexName: testIndexName, queryVector: createVector(0, 0.9), topK: 10, filter }),
228
- ).resolves.not.toThrow();
229
- }
230
- });
231
-
232
- it('rejects queries with empty object field values', async () => {
233
- const emptyFilters = { field: {} };
234
- await expect(
235
- vectorDB.query({ indexName: testIndexName, queryVector: createVector(0, 0.9), topK: 10, filter: emptyFilters }),
236
- ).rejects.toThrow();
237
- });
238
-
239
- it('rejects oversized filter queries', async () => {
240
- const largeFilter = {
241
- field1: { $in: Array(1000).fill('test') },
242
- field2: { $in: Array(1000).fill(123) },
243
- };
244
-
245
- await expect(
246
- vectorDB.query({ indexName: testIndexName, queryVector: createVector(0, 0.9), topK: 10, filter: largeFilter }),
247
- ).rejects.toThrow();
248
- });
249
-
250
- it('rejects queries with array values in comparison operators', async () => {
251
- await expect(
252
- vectorDB.query({
253
- indexName: testIndexName,
254
- queryVector: createVector(0, 0.9),
255
- topK: 10,
256
- filter: { field: { $gt: [] } },
257
- }),
258
- ).rejects.toThrow();
259
-
260
- await expect(
261
- vectorDB.query({
262
- indexName: testIndexName,
263
- queryVector: createVector(0, 0.9),
264
- topK: 10,
265
- filter: { field: { $lt: [1, 2, 3] } },
266
- }),
267
- ).rejects.toThrow();
268
- });
269
- });
270
-
271
- describe('Metadata Filter Tests', () => {
272
- beforeAll(async () => {
273
- await vectorDB.createIndex({ indexName: testIndexName2, dimension: VECTOR_DIMENSION, metric: 'cosine' });
274
- await waitUntilReady(vectorDB, testIndexName2);
275
-
276
- await vectorDB.createMetadataIndex(testIndexName2, 'price', 'number');
277
- await vectorDB.createMetadataIndex(testIndexName2, 'category', 'string');
278
- await vectorDB.createMetadataIndex(testIndexName2, 'rating', 'number');
279
- await vectorDB.createMetadataIndex(testIndexName2, 'nested.number', 'number');
280
- await vectorDB.createMetadataIndex(testIndexName2, 'nested.string', 'string');
281
- await vectorDB.createMetadataIndex(testIndexName2, 'nested.boolean', 'boolean');
282
- await vectorDB.createMetadataIndex(testIndexName2, 'isActive', 'boolean');
283
- await vectorDB.createMetadataIndex(testIndexName2, 'code', 'string');
284
- await vectorDB.createMetadataIndex(testIndexName2, 'optionalField', 'string');
285
- await vectorDB.createMetadataIndex(testIndexName2, 'mixedField', 'string');
286
-
287
- await waitForMetadataIndexes(vectorDB, testIndexName2, 10);
288
-
289
- // Create all test vectors and metadata at once
290
- const vectors = [
291
- // Base test vectors
292
- createVector(0, 1.0),
293
- createVector(1, 1.0),
294
- createVector(2, 1.0),
295
- createVector(3, 1.0),
296
- ];
297
-
298
- const metadata = [
299
- // Base test metadata
300
- {
301
- price: 100,
302
- category: 'electronics',
303
- rating: 4.5,
304
- nested: {
305
- number: 100,
306
- string: 'premium',
307
- boolean: true,
308
- },
309
- isActive: true,
310
- mixedField: 'string value',
311
- code: 'A123',
312
- optionalField: 'exists',
313
- },
314
- {
315
- price: 200,
316
- category: 'electronics',
317
- rating: 3.8,
318
- nested: {
319
- number: 200,
320
- string: 'premium',
321
- boolean: false,
322
- },
323
- isActive: false,
324
- mixedField: 10,
325
- code: 'B456',
326
- optionalField: null,
327
- },
328
- {
329
- price: 150,
330
- category: 'accessories',
331
- rating: 4.2,
332
- nested: {
333
- number: 150,
334
- string: 'premium',
335
- boolean: true,
336
- },
337
- isActive: false,
338
- mixedField: false,
339
- code: 'C789',
340
- },
341
- {
342
- price: 75,
343
- category: 'accessories',
344
- rating: 0,
345
- nested: {
346
- number: 75,
347
- string: 'basic',
348
- boolean: false,
349
- },
350
- isActive: false,
351
- mixedField: true,
352
- },
353
- ];
354
-
355
- await vectorDB.upsert({ indexName: testIndexName2, vectors, metadata });
356
- await waitUntilVectorsIndexed(vectorDB, testIndexName2, vectors.length);
357
-
358
- const stats = await vectorDB.describeIndex(testIndexName2);
359
- expect(stats.count).toBe(vectors.length);
360
- }, 800000);
361
-
362
- afterAll(async () => {
363
- const currentMetadata = await vectorDB.listMetadataIndexes(testIndexName2);
364
- for (const { propertyName } of currentMetadata) {
365
- await vectorDB.deleteMetadataIndex(testIndexName2, propertyName as string);
366
- }
367
- await vectorDB.deleteIndex(testIndexName2);
368
- }, 800000);
369
-
370
- describe('Basic Equality Operators', () => {
371
- it('filters with $eq operator', async () => {
372
- const results = await vectorDB.query({
373
- indexName: testIndexName2,
374
- queryVector: createVector(0, 1.0),
375
- filter: { category: 'electronics' },
376
- });
377
- expect(results.length).toBe(2);
378
- results.forEach(result => {
379
- expect(result.metadata?.category).toBe('electronics');
380
- });
381
- });
382
-
383
- it('filters with $ne operator', async () => {
384
- const results = await vectorDB.query({
385
- indexName: testIndexName2,
386
- queryVector: createVector(0, 1.0),
387
- filter: { category: { $ne: 'electronics' } },
388
- });
389
- expect(results.length).toBe(2);
390
- results.forEach(result => {
391
- expect(result.metadata?.category).not.toBe('electronics');
392
- });
393
- });
394
- });
395
-
396
- describe('Numeric Comparison Operators', () => {
397
- it('filters with $gt operator', async () => {
398
- const results = await vectorDB.query({
399
- indexName: testIndexName2,
400
- queryVector: createVector(0, 1.0),
401
- filter: { price: { $gt: 150 } },
402
- });
403
- expect(results.length).toBe(1);
404
- results.forEach(result => {
405
- expect(Number(result.metadata?.price)).toBeGreaterThan(150);
406
- });
407
- });
408
-
409
- it('filters with $gte operator', async () => {
410
- const results = await vectorDB.query({
411
- indexName: testIndexName2,
412
- queryVector: createVector(0, 1.0),
413
- filter: { price: { $gte: 100 } },
414
- });
415
- expect(results.length).toBe(3);
416
- results.forEach(result => {
417
- const price = Number(result.metadata?.price);
418
- expect(price).toBeGreaterThanOrEqual(100);
419
- });
420
- });
421
-
422
- it('filters with $lt operator', async () => {
423
- const results = await vectorDB.query({
424
- indexName: testIndexName2,
425
- queryVector: createVector(0, 1.0),
426
- filter: { price: { $lt: 150 } },
427
- });
428
- expect(results.length).toBe(2);
429
- results.forEach(result => {
430
- const price = Number(result.metadata?.price);
431
- expect(price).toBeLessThan(150);
432
- });
433
- });
434
-
435
- it('filters with $lte operator', async () => {
436
- const results = await vectorDB.query({
437
- indexName: testIndexName2,
438
- queryVector: createVector(0, 1.0),
439
- filter: { price: { $lte: 150 } },
440
- });
441
- expect(results.length).toBe(3);
442
- results.forEach(result => {
443
- const price = Number(result.metadata?.price);
444
- expect(price).toBeLessThanOrEqual(150);
445
- });
446
- });
447
- });
448
-
449
- describe('Array Operators', () => {
450
- it('filters with $in operator for exact matches', async () => {
451
- const results = await vectorDB.query({
452
- indexName: testIndexName2,
453
- queryVector: createVector(0, 1.0),
454
- filter: { category: { $in: ['electronics'] } },
455
- });
456
- expect(results.length).toBe(2);
457
- results.forEach(result => {
458
- expect(result.metadata?.category).toContain('electronics');
459
- });
460
- });
461
-
462
- it('filters with $nin operator', async () => {
463
- const results = await vectorDB.query({
464
- indexName: testIndexName2,
465
- queryVector: createVector(0, 1.0),
466
- filter: { category: { $nin: ['electronics'] } },
467
- });
468
- expect(results.length).toBe(2);
469
- results.forEach(result => {
470
- expect(result.metadata?.category).not.toContain('electronics');
471
- });
472
- });
473
- });
474
-
475
- describe('Boolean Operations', () => {
476
- it('filters with boolean values', async () => {
477
- const results = await vectorDB.query({
478
- indexName: testIndexName2,
479
- queryVector: createVector(0, 1.0),
480
- filter: { isActive: true },
481
- });
482
- expect(results.length).toBe(1);
483
- expect(results[0]?.metadata?.isActive).toBe(true);
484
- }, 5000);
485
-
486
- it('filters with $ne on boolean values', async () => {
487
- const results = await vectorDB.query({
488
- indexName: testIndexName2,
489
- queryVector: createVector(0, 1.0),
490
- filter: { isActive: { $ne: true } },
491
- });
492
- expect(results.length).toBe(3);
493
- results.forEach(result => {
494
- expect(result.metadata?.isActive).toBe(false);
495
- });
496
- }, 5000);
497
- });
498
-
499
- describe('Nested Field Operations', () => {
500
- it('filters on nested fields with comparison operators', async () => {
501
- const results = await vectorDB.query({
502
- indexName: testIndexName2,
503
- queryVector: createVector(0, 1.0),
504
- filter: { 'nested.number': { $gt: 100 } },
505
- });
506
- expect(results.length).toBe(2);
507
- results.forEach(result => {
508
- expect(result.metadata?.nested?.number).toBeGreaterThan(100);
509
- });
510
- });
511
-
512
- it('combines nested field filters with top-level filters', async () => {
513
- const results = await vectorDB.query({
514
- indexName: testIndexName2,
515
- queryVector: createVector(0, 1.0),
516
- filter: { 'nested.number': { $lt: 200 }, category: 'electronics' },
517
- });
518
- expect(results.length).toBe(1);
519
- expect(results[0]?.metadata?.nested?.number).toBeLessThan(200);
520
- expect(results[0]?.metadata?.category).toBe('electronics');
521
- });
522
-
523
- it('handles nested string equality', async () => {
524
- const results = await vectorDB.query({
525
- indexName: testIndexName2,
526
- queryVector: createVector(0, 1.0),
527
- filter: { 'nested.string': 'premium' },
528
- });
529
- expect(results.length).toBe(3);
530
- results.forEach(result => {
531
- expect(result.metadata?.nested?.string).toBe('premium');
532
- });
533
- }, 10000);
534
-
535
- it('combines nested numeric and boolean conditions', async () => {
536
- const results = await vectorDB.query({
537
- indexName: testIndexName2,
538
- queryVector: createVector(0, 1.0),
539
- filter: { 'nested.number': { $gt: 100 }, 'nested.boolean': true },
540
- });
541
- expect(results.length).toBe(1);
542
- expect(results[0]?.metadata?.nested?.number).toBeGreaterThan(100);
543
- expect(results[0]?.metadata?.nested?.boolean).toBe(true);
544
- }, 10000);
545
-
546
- it('handles multiple nested field comparisons', async () => {
547
- const results = await vectorDB.query({
548
- indexName: testIndexName2,
549
- queryVector: createVector(0, 1.0),
550
- filter: { 'nested.string': 'premium', 'nested.number': { $lt: 200 }, 'nested.boolean': true },
551
- });
552
- expect(results.length).toBe(2);
553
- const result = results[0]?.metadata?.nested;
554
- expect(result?.string).toBe('premium');
555
- expect(result?.number).toBeLessThan(200);
556
- expect(result?.boolean).toBe(true);
557
- }, 10000);
558
-
559
- it('handles $in with nested string values', async () => {
560
- const results = await vectorDB.query({
561
- indexName: testIndexName2,
562
- queryVector: createVector(0, 1.0),
563
- filter: { 'nested.string': { $in: ['premium', 'basic'] } },
564
- });
565
- expect(results.length).toBe(4);
566
- results.forEach(result => {
567
- expect(['premium', 'basic']).toContain(result.metadata?.nested?.string);
568
- });
569
- }, 10000);
570
- });
571
-
572
- describe('String Operations', () => {
573
- it('handles string numbers in numeric comparisons', async () => {
574
- const results = await vectorDB.query({
575
- indexName: testIndexName2,
576
- queryVector: createVector(0, 1.0),
577
- filter: { price: { $gt: '150' } }, // String number
578
- });
579
- expect(results.length).toBe(1);
580
- expect(Number(results[0]?.metadata?.price)).toBeGreaterThan(150);
581
- });
582
-
583
- it('handles mixed numeric and string comparisons', async () => {
584
- const results = await vectorDB.query({
585
- indexName: testIndexName2,
586
- queryVector: createVector(0, 1.0),
587
- filter: { price: { $gt: 100 }, category: { $in: ['electronics'] } },
588
- });
589
- expect(results.length).toBe(1);
590
- expect(Number(results[0]?.metadata?.price)).toBeGreaterThan(100);
591
- expect(results[0]?.metadata?.category).toBe('electronics');
592
- });
593
- });
594
-
595
- describe('Filter Validation and Edge Cases', () => {
596
- it('handles numeric zero values correctly', async () => {
597
- const results = await vectorDB.query({
598
- indexName: testIndexName2,
599
- queryVector: createVector(0, 1.0),
600
- filter: { rating: { $eq: 0 } },
601
- });
602
- expect(results.length).toBe(1);
603
- expect(results[0]?.metadata?.rating).toBe(0);
604
- });
605
-
606
- it('handles multiple conditions on same field', async () => {
607
- const results = await vectorDB.query({
608
- indexName: testIndexName2,
609
- queryVector: createVector(0, 1.0),
610
- filter: { price: { $gt: 75, $lt: 200 } },
611
- });
612
- expect(results.length).toBe(2);
613
- results.forEach(result => {
614
- const price = Number(result.metadata?.price);
615
- expect(price).toBeGreaterThan(75);
616
- expect(price).toBeLessThan(200);
617
- });
618
- });
619
-
620
- it('handles exact numeric equality', async () => {
621
- const results = await vectorDB.query({
622
- indexName: testIndexName2,
623
- queryVector: createVector(0, 1.0),
624
- filter: { price: { $eq: 100 } },
625
- });
626
- expect(results.length).toBe(1);
627
- expect(results[0]?.metadata?.price).toBe(100);
628
- });
629
-
630
- it('handles boundary conditions in ranges', async () => {
631
- const results = await vectorDB.query({
632
- indexName: testIndexName2,
633
- queryVector: createVector(0, 1.0),
634
- filter: { price: { $gte: 75, $lte: 75 } },
635
- });
636
- expect(results.length).toBe(1);
637
- expect(results[0]?.metadata?.price).toBe(75);
638
- });
639
- });
640
-
641
- describe('String Range Queries', () => {
642
- it('handles lexicographical ordering in string range queries', async () => {
643
- const results = await vectorDB.query({
644
- indexName: testIndexName2,
645
- queryVector: createVector(0, 1.0),
646
- filter: { code: { $gt: 'A123', $lt: 'C789' } },
647
- });
648
- expect(results.length).toBe(1);
649
- expect(results[0]?.metadata?.code).toBe('B456');
650
- }, 5000);
651
-
652
- it('handles string range queries with special characters', async () => {
653
- const results = await vectorDB.query({
654
- indexName: testIndexName2,
655
- queryVector: createVector(0, 1.0),
656
- filter: { code: { $gte: 'A', $lt: 'C' } },
657
- });
658
- expect(results.length).toBe(2);
659
- results.forEach(result => {
660
- expect(result.metadata?.code).toMatch(/^[AB]/);
661
- });
662
- }, 5000);
663
- });
664
-
665
- describe('Null and Special Values', () => {
666
- it('handles $in with null values', async () => {
667
- const results = await vectorDB.query({
668
- indexName: testIndexName2,
669
- queryVector: createVector(0, 1.0),
670
- filter: { optionalField: { $in: [null, 'exists'] } },
671
- });
672
- expect(results.length).toBe(1);
673
- }, 5000);
674
-
675
- it('handles $ne with null values', async () => {
676
- const results = await vectorDB.query({
677
- indexName: testIndexName2,
678
- queryVector: createVector(0, 1.0),
679
- filter: { optionalField: { $ne: null } },
680
- });
681
- expect(results.length).toBe(4);
682
- expect(results[0]?.metadata?.optionalField).toBe('exists');
683
- }, 5000);
684
- });
685
-
686
- describe('Mixed Type Arrays and Values', () => {
687
- it('handles $in with mixed type arrays', async () => {
688
- const results = await vectorDB.query({
689
- indexName: testIndexName2,
690
- queryVector: createVector(0, 1.0),
691
- filter: { mixedField: { $in: ['string value', 10, null] } },
692
- });
693
- expect(results.length).toBe(2);
694
- }, 5000);
695
-
696
- it('combines different types of filters', async () => {
697
- const results = await vectorDB.query({
698
- indexName: testIndexName2,
699
- queryVector: createVector(0, 1.0),
700
- filter: { mixedField: { $in: ['string value', true] }, price: { $eq: 100 } },
701
- });
702
- expect(results.length).toBe(1);
703
- }, 5000);
704
- });
705
-
706
- describe('Filter Size and Structure Validation', () => {
707
- it('handles filters approaching size limit', async () => {
708
- // Create a filter that's close to but under 2048 bytes
709
- const longString = 'a'.repeat(400);
710
- const filter = {
711
- category: { $in: [longString, longString.slice(0, 100)] },
712
- price: { $gt: 0, $lt: 1000 },
713
- 'nested.string': longString.slice(0, 200),
714
- };
715
-
716
- await expect(
717
- vectorDB.query({
718
- indexName: testIndexName2,
719
- queryVector: createVector(0, 1.0),
720
- filter,
721
- }),
722
- ).resolves.toBeDefined();
723
- }, 5000);
724
-
725
- it('handles valid range query combinations', async () => {
726
- const validRangeCombinations = [
727
- { price: { $gt: 0, $lt: 1000 } },
728
- { price: { $gte: 100, $lte: 200 } },
729
- { price: { $gt: 0, $lte: 1000 } },
730
- { price: { $gte: 0, $lt: 1000 } },
731
- ];
732
-
733
- for (const filter of validRangeCombinations) {
734
- await expect(
735
- vectorDB.query({
736
- indexName: testIndexName2,
737
- queryVector: createVector(0, 1.0),
738
- filter,
739
- }),
740
- ).resolves.toBeDefined();
741
- }
742
- }, 5000);
743
-
744
- it('should handle undefined filter', async () => {
745
- const results1 = await vectorDB.query({
746
- indexName: testIndexName2,
747
- queryVector: createVector(0, 1.0),
748
- filter: undefined,
749
- });
750
- const results2 = await vectorDB.query({
751
- indexName: testIndexName2,
752
- queryVector: createVector(0, 1.0),
753
- });
754
- expect(results1).toEqual(results2);
755
- expect(results1.length).toBeGreaterThan(0);
756
- });
757
-
758
- it('should handle empty object filter', async () => {
759
- const results = await vectorDB.query({
760
- indexName: testIndexName2,
761
- queryVector: createVector(0, 1.0),
762
- filter: {},
763
- });
764
- const results2 = await vectorDB.query({
765
- indexName: testIndexName2,
766
- queryVector: createVector(0, 1.0),
767
- });
768
- expect(results).toEqual(results2);
769
- expect(results.length).toBeGreaterThan(0);
770
- });
771
-
772
- it('should handle null filter', async () => {
773
- const results = await vectorDB.query({
774
- indexName: testIndexName2,
775
- queryVector: createVector(0, 1.0),
776
- filter: null,
777
- });
778
- const results2 = await vectorDB.query({
779
- indexName: testIndexName2,
780
- queryVector: createVector(0, 1.0),
781
- });
782
- expect(results).toEqual(results2);
783
- expect(results.length).toBeGreaterThan(0);
784
- });
785
- });
786
- }, 800000);
787
- describe('Deprecation Warnings', () => {
788
- const indexName = 'testdeprecationwarnings';
789
-
790
- const indexName2 = 'testdeprecationwarnings2';
791
-
792
- const indexName3 = 'testdeprecationwarnings3';
793
-
794
- const indexName4 = 'testdeprecationwarnings4';
795
-
796
- let warnSpy;
797
-
798
- beforeAll(async () => {
799
- try {
800
- await vectorDB.deleteIndex(indexName);
801
- } catch {
802
- // Ignore errors if index doesn't exist
803
- }
804
- try {
805
- await vectorDB.deleteIndex(indexName2);
806
- } catch {
807
- // Ignore errors if index doesn't exist
808
- }
809
- try {
810
- await vectorDB.deleteIndex(indexName3);
811
- } catch {
812
- // Ignore errors if index doesn't exist
813
- }
814
- try {
815
- await vectorDB.deleteIndex(indexName4);
816
- } catch {
817
- // Ignore errors if index doesn't exist
818
- }
819
- await vectorDB.createIndex({ indexName: indexName, dimension: VECTOR_DIMENSION });
820
- await waitUntilReady(vectorDB, indexName);
821
- });
822
-
823
- afterAll(async () => {
824
- try {
825
- await vectorDB.deleteIndex(indexName);
826
- } catch {
827
- // Ignore errors if index doesn't exist
828
- }
829
- try {
830
- await vectorDB.deleteIndex(indexName2);
831
- } catch {
832
- // Ignore errors if index doesn't exist
833
- }
834
- try {
835
- await vectorDB.deleteIndex(indexName3);
836
- } catch {
837
- // Ignore errors if index doesn't exist
838
- }
839
- try {
840
- await vectorDB.deleteIndex(indexName4);
841
- } catch {
842
- // Ignore errors if index doesn't exist
843
- }
844
- });
845
-
846
- beforeEach(async () => {
847
- warnSpy = vi.spyOn(vectorDB['logger'], 'warn');
848
- });
849
-
850
- afterEach(async () => {
851
- warnSpy.mockRestore();
852
- try {
853
- await vectorDB.deleteIndex(indexName2);
854
- } catch (error) {
855
- console.warn('Failed to delete test index:', error);
856
- }
857
- });
858
-
859
- it('should show deprecation warning when using individual args for createIndex', async () => {
860
- await vectorDB.createIndex(indexName2, VECTOR_DIMENSION, 'cosine');
861
- await waitUntilReady(vectorDB, indexName2);
862
- expect(warnSpy).toHaveBeenCalledWith(
863
- expect.stringContaining('Deprecation Warning: Passing individual arguments to createIndex() is deprecated'),
864
- );
865
- });
866
-
867
- it('should show deprecation warning when using individual args for upsert', async () => {
868
- await vectorDB.upsert(indexName, [createVector(0, 1.0)], [{ test: 'data' }]);
869
-
870
- expect(warnSpy).toHaveBeenCalledWith(
871
- expect.stringContaining('Deprecation Warning: Passing individual arguments to upsert() is deprecated'),
872
- );
873
- });
874
-
875
- it('should show deprecation warning when using individual args for query', async () => {
876
- await vectorDB.query(indexName, createVector(0, 1.0), 5);
877
-
878
- expect(warnSpy).toHaveBeenCalledWith(
879
- expect.stringContaining('Deprecation Warning: Passing individual arguments to query() is deprecated'),
880
- );
881
- });
882
-
883
- it('should not show deprecation warning when using object param for query', async () => {
884
- await vectorDB.query({
885
- indexName,
886
- queryVector: createVector(0, 1.0),
887
- topK: 5,
888
- });
889
-
890
- expect(warnSpy).not.toHaveBeenCalled();
891
- });
892
-
893
- it('should not show deprecation warning when using object param for createIndex', async () => {
894
- await vectorDB.createIndex({
895
- indexName: indexName3,
896
- dimension: VECTOR_DIMENSION,
897
- metric: 'cosine',
898
- });
899
-
900
- expect(warnSpy).not.toHaveBeenCalled();
901
- });
902
-
903
- it('should not show deprecation warning when using object param for upsert', async () => {
904
- await vectorDB.upsert({
905
- indexName,
906
- vectors: [createVector(0, 1.0)],
907
- metadata: [{ test: 'data' }],
908
- });
909
-
910
- expect(warnSpy).not.toHaveBeenCalled();
911
- });
912
-
913
- it('should maintain backward compatibility with individual args', async () => {
914
- // Query
915
- const queryResults = await vectorDB.query(indexName, createVector(0, 1.0), 5);
916
- expect(Array.isArray(queryResults)).toBe(true);
917
-
918
- // CreateIndex
919
- await expect(vectorDB.createIndex(indexName4, VECTOR_DIMENSION, 'cosine')).resolves.not.toThrow();
920
- await waitUntilReady(vectorDB, indexName4);
921
- // Upsert
922
- const upsertResults = await vectorDB.upsert({
923
- indexName,
924
- vectors: [createVector(0, 1.0)],
925
- metadata: [{ test: 'data' }],
926
- });
927
- expect(Array.isArray(upsertResults)).toBe(true);
928
- expect(upsertResults).toHaveLength(1);
929
- });
930
- }, 80000);
931
- });