@mastra/opensearch 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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +24 -0
- package/dist/_tsup-dts-rollup.d.cts +18 -5
- package/dist/_tsup-dts-rollup.d.ts +18 -5
- package/dist/index.cjs +128 -53
- package/dist/index.js +120 -45
- package/package.json +4 -4
- package/src/vector/filter.test.ts +55 -54
- package/src/vector/filter.ts +23 -5
- package/src/vector/index.test.ts +4 -5
- package/src/vector/index.ts +134 -55
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import type { OpenSearchVectorFilter } from './filter';
|
|
3
4
|
import { OpenSearchFilterTranslator } from './filter';
|
|
4
5
|
|
|
5
6
|
describe('OpenSearchFilterTranslator', () => {
|
|
@@ -13,19 +14,19 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
13
14
|
describe('basic operations', () => {
|
|
14
15
|
it('handles empty filters', () => {
|
|
15
16
|
expect(translator.translate({})).toEqual(undefined);
|
|
16
|
-
expect(translator.translate(null
|
|
17
|
-
expect(translator.translate(undefined
|
|
17
|
+
expect(translator.translate(null)).toEqual(undefined);
|
|
18
|
+
expect(translator.translate(undefined)).toEqual(undefined);
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
it('translates simple field equality to term query', () => {
|
|
21
|
-
const filter = { field: 'value' };
|
|
22
|
+
const filter: OpenSearchVectorFilter = { field: 'value' };
|
|
22
23
|
expect(translator.translate(filter)).toEqual({
|
|
23
24
|
term: { 'metadata.field.keyword': 'value' },
|
|
24
25
|
});
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
it('translates multiple top-level fields to bool must', () => {
|
|
28
|
-
const filter = { field1: 'value1', field2: 'value2' };
|
|
29
|
+
const filter: OpenSearchVectorFilter = { field1: 'value1', field2: 'value2' };
|
|
29
30
|
expect(translator.translate(filter)).toEqual({
|
|
30
31
|
bool: {
|
|
31
32
|
must: [{ term: { 'metadata.field1.keyword': 'value1' } }, { term: { 'metadata.field2.keyword': 'value2' } }],
|
|
@@ -62,14 +63,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
62
63
|
// Comparison Operators
|
|
63
64
|
describe('comparison operators', () => {
|
|
64
65
|
it('translates $eq operator', () => {
|
|
65
|
-
const filter = { field: { $eq: 'value' } };
|
|
66
|
+
const filter: OpenSearchVectorFilter = { field: { $eq: 'value' } };
|
|
66
67
|
expect(translator.translate(filter)).toEqual({
|
|
67
68
|
term: { 'metadata.field.keyword': 'value' },
|
|
68
69
|
});
|
|
69
70
|
});
|
|
70
71
|
|
|
71
72
|
it('translates $ne operator', () => {
|
|
72
|
-
const filter = { field: { $ne: 'value' } };
|
|
73
|
+
const filter: OpenSearchVectorFilter = { field: { $ne: 'value' } };
|
|
73
74
|
expect(translator.translate(filter)).toEqual({
|
|
74
75
|
bool: {
|
|
75
76
|
must_not: [{ term: { 'metadata.field.keyword': 'value' } }],
|
|
@@ -79,7 +80,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
79
80
|
|
|
80
81
|
it('handles date values', () => {
|
|
81
82
|
const date = new Date('2024-01-01');
|
|
82
|
-
const filter = { timestamp: { $gt: date } };
|
|
83
|
+
const filter: OpenSearchVectorFilter = { timestamp: { $gt: date } };
|
|
83
84
|
expect(translator.translate(filter)).toEqual({
|
|
84
85
|
range: { 'metadata.timestamp': { gt: date.toISOString() } },
|
|
85
86
|
});
|
|
@@ -89,7 +90,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
89
90
|
// Logical Operators
|
|
90
91
|
describe('logical operators', () => {
|
|
91
92
|
it('translates $and operator', () => {
|
|
92
|
-
const filter = {
|
|
93
|
+
const filter: OpenSearchVectorFilter = {
|
|
93
94
|
$and: [{ field1: 'value1' }, { field2: 'value2' }],
|
|
94
95
|
};
|
|
95
96
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -100,7 +101,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
it('translates $or operator', () => {
|
|
103
|
-
const filter = {
|
|
104
|
+
const filter: OpenSearchVectorFilter = {
|
|
104
105
|
$or: [{ field1: 'value1' }, { field2: 'value2' }],
|
|
105
106
|
};
|
|
106
107
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -114,7 +115,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
it('translates $not operator', () => {
|
|
117
|
-
const filter = {
|
|
118
|
+
const filter: OpenSearchVectorFilter = {
|
|
118
119
|
$not: { field: 'value' },
|
|
119
120
|
};
|
|
120
121
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -125,7 +126,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
125
126
|
});
|
|
126
127
|
|
|
127
128
|
it('translates $not with $eq operator', () => {
|
|
128
|
-
const filter = { field: { $not: { $eq: 'value' } } };
|
|
129
|
+
const filter: OpenSearchVectorFilter = { field: { $not: { $eq: 'value' } } };
|
|
129
130
|
expect(translator.translate(filter)).toEqual({
|
|
130
131
|
bool: {
|
|
131
132
|
must_not: [{ term: { 'metadata.field.keyword': 'value' } }],
|
|
@@ -134,7 +135,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
it('translates $not with $ne operator', () => {
|
|
137
|
-
const filter = { field: { $not: { $ne: 'value' } } };
|
|
138
|
+
const filter: OpenSearchVectorFilter = { field: { $not: { $ne: 'value' } } };
|
|
138
139
|
expect(translator.translate(filter)).toEqual({
|
|
139
140
|
bool: {
|
|
140
141
|
must_not: [
|
|
@@ -149,14 +150,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
149
150
|
});
|
|
150
151
|
|
|
151
152
|
it('translates $not with $eq null', () => {
|
|
152
|
-
const filter = { field: { $not: { $eq: null } } };
|
|
153
|
+
const filter: OpenSearchVectorFilter = { field: { $not: { $eq: null } } };
|
|
153
154
|
expect(translator.translate(filter)).toEqual({
|
|
154
155
|
exists: { field: 'metadata.field' },
|
|
155
156
|
});
|
|
156
157
|
});
|
|
157
158
|
|
|
158
159
|
it('translates $not with $ne null', () => {
|
|
159
|
-
const filter = { field: { $not: { $ne: null } } };
|
|
160
|
+
const filter: OpenSearchVectorFilter = { field: { $not: { $ne: null } } };
|
|
160
161
|
expect(translator.translate(filter)).toEqual({
|
|
161
162
|
bool: {
|
|
162
163
|
must_not: [{ exists: { field: 'metadata.field' } }],
|
|
@@ -165,7 +166,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
165
166
|
});
|
|
166
167
|
|
|
167
168
|
it('translates $not with nested fields', () => {
|
|
168
|
-
const filter = { 'user.profile.age': { $not: { $gt: 25 } } };
|
|
169
|
+
const filter: OpenSearchVectorFilter = { 'user.profile.age': { $not: { $gt: 25 } } };
|
|
169
170
|
expect(translator.translate(filter)).toEqual({
|
|
170
171
|
bool: {
|
|
171
172
|
must_not: [
|
|
@@ -178,7 +179,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
178
179
|
});
|
|
179
180
|
|
|
180
181
|
it('translates $not with multiple operators', () => {
|
|
181
|
-
const filter = { price: { $not: { $gte: 30, $lte: 70 } } };
|
|
182
|
+
const filter: OpenSearchVectorFilter = { price: { $not: { $gte: 30, $lte: 70 } } };
|
|
182
183
|
expect(translator.translate(filter)).toEqual({
|
|
183
184
|
bool: {
|
|
184
185
|
must_not: [
|
|
@@ -191,7 +192,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
it('handles empty $and array', () => {
|
|
194
|
-
const filter = {
|
|
195
|
+
const filter: OpenSearchVectorFilter = {
|
|
195
196
|
$and: [],
|
|
196
197
|
};
|
|
197
198
|
// Empty $and should match everything
|
|
@@ -199,7 +200,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
199
200
|
});
|
|
200
201
|
|
|
201
202
|
it('handles empty $or array', () => {
|
|
202
|
-
const filter = {
|
|
203
|
+
const filter: OpenSearchVectorFilter = {
|
|
203
204
|
$or: [],
|
|
204
205
|
};
|
|
205
206
|
// Empty $or should match nothing
|
|
@@ -218,7 +219,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
218
219
|
});
|
|
219
220
|
|
|
220
221
|
it('handles $not with comparison operators', () => {
|
|
221
|
-
const filter = {
|
|
222
|
+
const filter: OpenSearchVectorFilter = {
|
|
222
223
|
price: { $not: { $gt: 100 } },
|
|
223
224
|
};
|
|
224
225
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -229,7 +230,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
229
230
|
});
|
|
230
231
|
|
|
231
232
|
it('handles nested $not with $or', () => {
|
|
232
|
-
const filter = {
|
|
233
|
+
const filter: OpenSearchVectorFilter = {
|
|
233
234
|
$not: { $or: [{ category: 'electronics' }, { category: 'books' }] },
|
|
234
235
|
};
|
|
235
236
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -249,7 +250,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
249
250
|
});
|
|
250
251
|
|
|
251
252
|
it('handles $not with $not operator', () => {
|
|
252
|
-
const filter = {
|
|
253
|
+
const filter: OpenSearchVectorFilter = {
|
|
253
254
|
$not: { $not: { category: 'electronics' } },
|
|
254
255
|
};
|
|
255
256
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -266,7 +267,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
266
267
|
});
|
|
267
268
|
|
|
268
269
|
it('handles nested logical operators', () => {
|
|
269
|
-
const filter = {
|
|
270
|
+
const filter: OpenSearchVectorFilter = {
|
|
270
271
|
$and: [
|
|
271
272
|
{ field1: 'value1' },
|
|
272
273
|
{
|
|
@@ -295,14 +296,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
295
296
|
// Array Operators
|
|
296
297
|
describe('array operators', () => {
|
|
297
298
|
it('translates $in operator', () => {
|
|
298
|
-
const filter = { field: { $in: ['value1', 'value2'] } };
|
|
299
|
+
const filter: OpenSearchVectorFilter = { field: { $in: ['value1', 'value2'] } };
|
|
299
300
|
expect(translator.translate(filter)).toEqual({
|
|
300
301
|
terms: { 'metadata.field.keyword': ['value1', 'value2'] },
|
|
301
302
|
});
|
|
302
303
|
});
|
|
303
304
|
|
|
304
305
|
it('translates $nin operator', () => {
|
|
305
|
-
const filter = { field: { $nin: ['value1', 'value2'] } };
|
|
306
|
+
const filter: OpenSearchVectorFilter = { field: { $nin: ['value1', 'value2'] } };
|
|
306
307
|
expect(translator.translate(filter)).toEqual({
|
|
307
308
|
bool: {
|
|
308
309
|
must_not: [{ terms: { 'metadata.field.keyword': ['value1', 'value2'] } }],
|
|
@@ -311,7 +312,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
311
312
|
});
|
|
312
313
|
|
|
313
314
|
it('translates $all operator', () => {
|
|
314
|
-
const filter = { field: { $all: ['value1', 'value2'] } };
|
|
315
|
+
const filter: OpenSearchVectorFilter = { field: { $all: ['value1', 'value2'] } };
|
|
315
316
|
expect(translator.translate(filter)).toEqual({
|
|
316
317
|
bool: {
|
|
317
318
|
must: [{ term: { 'metadata.field.keyword': 'value1' } }, { term: { 'metadata.field.keyword': 'value2' } }],
|
|
@@ -320,7 +321,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
320
321
|
});
|
|
321
322
|
|
|
322
323
|
it('handles empty $in array', () => {
|
|
323
|
-
const filter = { field: { $in: [] } };
|
|
324
|
+
const filter: OpenSearchVectorFilter = { field: { $in: [] } };
|
|
324
325
|
// Empty $in should match nothing (empty terms)
|
|
325
326
|
expect(translator.translate(filter)).toEqual({
|
|
326
327
|
terms: { 'metadata.field.keyword': [] },
|
|
@@ -328,7 +329,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
328
329
|
});
|
|
329
330
|
|
|
330
331
|
it('handles empty $nin array', () => {
|
|
331
|
-
const filter = { field: { $nin: [] } };
|
|
332
|
+
const filter: OpenSearchVectorFilter = { field: { $nin: [] } };
|
|
332
333
|
// Empty $nin should match everything
|
|
333
334
|
expect(translator.translate(filter)).toEqual({
|
|
334
335
|
match_all: {},
|
|
@@ -336,7 +337,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
336
337
|
});
|
|
337
338
|
|
|
338
339
|
it('handles empty $all array', () => {
|
|
339
|
-
const filter = { field: { $all: [] } };
|
|
340
|
+
const filter: OpenSearchVectorFilter = { field: { $all: [] } };
|
|
340
341
|
// Empty $all should match nothing
|
|
341
342
|
expect(translator.translate(filter)).toEqual({
|
|
342
343
|
bool: {
|
|
@@ -346,7 +347,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
346
347
|
});
|
|
347
348
|
|
|
348
349
|
it('handles $not with array operators', () => {
|
|
349
|
-
const filter = { tags: { $not: { $in: ['premium', 'new'] } } };
|
|
350
|
+
const filter: OpenSearchVectorFilter = { tags: { $not: { $in: ['premium', 'new'] } } };
|
|
350
351
|
expect(translator.translate(filter)).toEqual({
|
|
351
352
|
bool: {
|
|
352
353
|
must_not: [
|
|
@@ -359,7 +360,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
359
360
|
});
|
|
360
361
|
|
|
361
362
|
it('handles $not with empty array operators', () => {
|
|
362
|
-
const filter = { tags: { $not: { $in: [] } } };
|
|
363
|
+
const filter: OpenSearchVectorFilter = { tags: { $not: { $in: [] } } };
|
|
363
364
|
expect(translator.translate(filter)).toEqual({
|
|
364
365
|
bool: {
|
|
365
366
|
must_not: [
|
|
@@ -375,14 +376,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
375
376
|
// Element Operators
|
|
376
377
|
describe('element operators', () => {
|
|
377
378
|
it('translates $exists operator', () => {
|
|
378
|
-
const filter = { field: { $exists: true } };
|
|
379
|
+
const filter: OpenSearchVectorFilter = { field: { $exists: true } };
|
|
379
380
|
expect(translator.translate(filter)).toEqual({
|
|
380
381
|
exists: { field: 'metadata.field' },
|
|
381
382
|
});
|
|
382
383
|
});
|
|
383
384
|
|
|
384
385
|
it('translates $exists operator with false', () => {
|
|
385
|
-
const filter = { field: { $exists: false } };
|
|
386
|
+
const filter: OpenSearchVectorFilter = { field: { $exists: false } };
|
|
386
387
|
expect(translator.translate(filter)).toEqual({
|
|
387
388
|
bool: {
|
|
388
389
|
must_not: [{ exists: { field: 'metadata.field' } }],
|
|
@@ -394,14 +395,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
394
395
|
// Regex Operators
|
|
395
396
|
describe('regex operators', () => {
|
|
396
397
|
it('translates $regex operator', () => {
|
|
397
|
-
const filter = { field: { $regex: 'pattern' } };
|
|
398
|
+
const filter: OpenSearchVectorFilter = { field: { $regex: 'pattern' } };
|
|
398
399
|
expect(translator.translate(filter)).toEqual({
|
|
399
400
|
regexp: { 'metadata.field': 'pattern' },
|
|
400
401
|
});
|
|
401
402
|
});
|
|
402
403
|
|
|
403
404
|
it('handles $regex with start anchor', () => {
|
|
404
|
-
const filter = { category: { $regex: '^elect' } };
|
|
405
|
+
const filter: OpenSearchVectorFilter = { category: { $regex: '^elect' } };
|
|
405
406
|
// Should use wildcard for better anchor handling
|
|
406
407
|
expect(translator.translate(filter)).toEqual({
|
|
407
408
|
wildcard: { 'metadata.category': 'elect*' },
|
|
@@ -409,7 +410,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
409
410
|
});
|
|
410
411
|
|
|
411
412
|
it('handles $regex with end anchor', () => {
|
|
412
|
-
const filter = { category: { $regex: 'nics$' } };
|
|
413
|
+
const filter: OpenSearchVectorFilter = { category: { $regex: 'nics$' } };
|
|
413
414
|
// Should use wildcard for better anchor handling
|
|
414
415
|
expect(translator.translate(filter)).toEqual({
|
|
415
416
|
wildcard: { 'metadata.category': '*nics' },
|
|
@@ -417,7 +418,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
417
418
|
});
|
|
418
419
|
|
|
419
420
|
it('handles $regex with both anchors', () => {
|
|
420
|
-
const filter = { category: { $regex: '^electronics$' } };
|
|
421
|
+
const filter: OpenSearchVectorFilter = { category: { $regex: '^electronics$' } };
|
|
421
422
|
// Should use exact match for both anchors
|
|
422
423
|
expect(translator.translate(filter)).toEqual({
|
|
423
424
|
wildcard: { 'metadata.category': 'electronics' },
|
|
@@ -425,7 +426,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
425
426
|
});
|
|
426
427
|
|
|
427
428
|
it('handles $not with $regex operator', () => {
|
|
428
|
-
const filter = { category: { $not: { $regex: '^elect' } } };
|
|
429
|
+
const filter: OpenSearchVectorFilter = { category: { $not: { $regex: '^elect' } } };
|
|
429
430
|
expect(translator.translate(filter)).toEqual({
|
|
430
431
|
bool: {
|
|
431
432
|
must_not: [
|
|
@@ -441,14 +442,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
441
442
|
// Complex Queries
|
|
442
443
|
describe('complex queries', () => {
|
|
443
444
|
it('translates numeric operators', () => {
|
|
444
|
-
const filter = { price: { $gt: 70, $lte: 100 } };
|
|
445
|
+
const filter: OpenSearchVectorFilter = { price: { $gt: 70, $lte: 100 } };
|
|
445
446
|
expect(translator.translate(filter)).toEqual({
|
|
446
447
|
range: { 'metadata.price': { gt: 70, lte: 100 } },
|
|
447
448
|
});
|
|
448
449
|
});
|
|
449
450
|
|
|
450
451
|
it('translates multiple range operators on the same field', () => {
|
|
451
|
-
const filter = { price: { $gte: 50, $lt: 200 } };
|
|
452
|
+
const filter: OpenSearchVectorFilter = { price: { $gte: 50, $lt: 200 } };
|
|
452
453
|
expect(translator.translate(filter)).toEqual({
|
|
453
454
|
range: { 'metadata.price': { gte: 50, lt: 200 } },
|
|
454
455
|
});
|
|
@@ -456,14 +457,14 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
456
457
|
|
|
457
458
|
it('translates all four range operators combined', () => {
|
|
458
459
|
// This is an edge case that would never occur in practice, but tests the implementation
|
|
459
|
-
const filter = { value: { $gt: 10, $gte: 20, $lt: 100, $lte: 90 } };
|
|
460
|
+
const filter: OpenSearchVectorFilter = { value: { $gt: 10, $gte: 20, $lt: 100, $lte: 90 } };
|
|
460
461
|
expect(translator.translate(filter)).toEqual({
|
|
461
462
|
range: { 'metadata.value': { gt: 10, gte: 20, lt: 100, lte: 90 } },
|
|
462
463
|
});
|
|
463
464
|
});
|
|
464
465
|
|
|
465
466
|
it('translates mixed numeric and non-numeric operators', () => {
|
|
466
|
-
const filter = { price: { $gt: 50, $exists: true } };
|
|
467
|
+
const filter: OpenSearchVectorFilter = { price: { $gt: 50, $exists: true } };
|
|
467
468
|
expect(translator.translate(filter)).toEqual({
|
|
468
469
|
bool: {
|
|
469
470
|
must: [{ range: { 'metadata.price': { gt: 50 } } }, { exists: { field: 'metadata.price' } }],
|
|
@@ -471,7 +472,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
471
472
|
});
|
|
472
473
|
});
|
|
473
474
|
it('translates mixed operators', () => {
|
|
474
|
-
const filter = {
|
|
475
|
+
const filter: OpenSearchVectorFilter = {
|
|
475
476
|
$and: [{ field1: { $gt: 10 } }, { field2: { $in: ['value1', 'value2'] } }, { field3: { $exists: true } }],
|
|
476
477
|
};
|
|
477
478
|
expect(translator.translate(filter)).toEqual({
|
|
@@ -486,7 +487,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
486
487
|
});
|
|
487
488
|
|
|
488
489
|
it('translates complex nested queries', () => {
|
|
489
|
-
const filter = {
|
|
490
|
+
const filter: OpenSearchVectorFilter = {
|
|
490
491
|
$and: [
|
|
491
492
|
{ status: 'active' },
|
|
492
493
|
{
|
|
@@ -542,7 +543,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
542
543
|
|
|
543
544
|
it('throws error for invalid array operator values', () => {
|
|
544
545
|
const filter = { field: { $in: 'not-an-array' } };
|
|
545
|
-
expect(() => translator.translate(filter)).toThrow();
|
|
546
|
+
expect(() => translator.translate(filter as any)).toThrow();
|
|
546
547
|
});
|
|
547
548
|
|
|
548
549
|
it('throws error for nested invalid operators', () => {
|
|
@@ -553,7 +554,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
553
554
|
|
|
554
555
|
describe('special values', () => {
|
|
555
556
|
it('handles boolean values', () => {
|
|
556
|
-
const filter = { active: true, disabled: false };
|
|
557
|
+
const filter: OpenSearchVectorFilter = { active: true, disabled: false };
|
|
557
558
|
expect(translator.translate(filter)).toEqual({
|
|
558
559
|
bool: {
|
|
559
560
|
must: [{ term: { 'metadata.active': true } }, { term: { 'metadata.disabled': false } }],
|
|
@@ -562,7 +563,7 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
562
563
|
});
|
|
563
564
|
|
|
564
565
|
it('handles null values', () => {
|
|
565
|
-
const filter = { field: null };
|
|
566
|
+
const filter: OpenSearchVectorFilter = { field: null };
|
|
566
567
|
expect(translator.translate(filter)).toEqual({
|
|
567
568
|
term: { 'metadata.field': null },
|
|
568
569
|
});
|
|
@@ -571,21 +572,21 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
571
572
|
|
|
572
573
|
describe('array handling', () => {
|
|
573
574
|
it('translates array values to terms query', () => {
|
|
574
|
-
const filter = { tags: ['premium', 'new'] };
|
|
575
|
+
const filter: OpenSearchVectorFilter = { tags: ['premium', 'new'] };
|
|
575
576
|
expect(translator.translate(filter)).toEqual({
|
|
576
577
|
terms: { 'metadata.tags.keyword': ['premium', 'new'] },
|
|
577
578
|
});
|
|
578
579
|
});
|
|
579
580
|
|
|
580
581
|
it('translates numeric array values to terms query', () => {
|
|
581
|
-
const filter = { scores: [90, 95, 100] };
|
|
582
|
+
const filter: OpenSearchVectorFilter = { scores: [90, 95, 100] };
|
|
582
583
|
expect(translator.translate(filter)).toEqual({
|
|
583
584
|
terms: { 'metadata.scores': [90, 95, 100] },
|
|
584
585
|
});
|
|
585
586
|
});
|
|
586
587
|
|
|
587
588
|
it('translates empty array values to empty terms query', () => {
|
|
588
|
-
const filter = { tags: [] };
|
|
589
|
+
const filter: OpenSearchVectorFilter = { tags: [] };
|
|
589
590
|
expect(translator.translate(filter)).toEqual({
|
|
590
591
|
terms: { 'metadata.tags.keyword': [] },
|
|
591
592
|
});
|
|
@@ -609,35 +610,35 @@ describe('OpenSearchFilterTranslator', () => {
|
|
|
609
610
|
|
|
610
611
|
describe('field type handling', () => {
|
|
611
612
|
it('adds .keyword suffix for string fields', () => {
|
|
612
|
-
const filter = { field: 'value' };
|
|
613
|
+
const filter: OpenSearchVectorFilter = { field: 'value' };
|
|
613
614
|
expect(translator.translate(filter)).toEqual({
|
|
614
615
|
term: { 'metadata.field.keyword': 'value' },
|
|
615
616
|
});
|
|
616
617
|
});
|
|
617
618
|
|
|
618
619
|
it('adds .keyword suffix for string array fields', () => {
|
|
619
|
-
const filter = { field: { $in: ['value1', 'value2'] } };
|
|
620
|
+
const filter: OpenSearchVectorFilter = { field: { $in: ['value1', 'value2'] } };
|
|
620
621
|
expect(translator.translate(filter)).toEqual({
|
|
621
622
|
terms: { 'metadata.field.keyword': ['value1', 'value2'] },
|
|
622
623
|
});
|
|
623
624
|
});
|
|
624
625
|
|
|
625
626
|
it('does not add .keyword suffix for numeric fields', () => {
|
|
626
|
-
const filter = { field: 123 };
|
|
627
|
+
const filter: OpenSearchVectorFilter = { field: 123 };
|
|
627
628
|
expect(translator.translate(filter)).toEqual({
|
|
628
629
|
term: { 'metadata.field': 123 },
|
|
629
630
|
});
|
|
630
631
|
});
|
|
631
632
|
|
|
632
633
|
it('does not add .keyword suffix for numeric array fields', () => {
|
|
633
|
-
const filter = { field: { $in: [1, 2, 3] } };
|
|
634
|
+
const filter: OpenSearchVectorFilter = { field: { $in: [1, 2, 3] } };
|
|
634
635
|
expect(translator.translate(filter)).toEqual({
|
|
635
636
|
terms: { 'metadata.field': [1, 2, 3] },
|
|
636
637
|
});
|
|
637
638
|
});
|
|
638
639
|
|
|
639
640
|
it('handles mixed field types in complex queries', () => {
|
|
640
|
-
const filter = {
|
|
641
|
+
const filter: OpenSearchVectorFilter = {
|
|
641
642
|
$and: [
|
|
642
643
|
{ textField: 'value' },
|
|
643
644
|
{ numericField: 123 },
|
package/src/vector/filter.ts
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BlacklistedRootOperators,
|
|
3
|
+
LogicalOperatorValueMap,
|
|
4
|
+
OperatorSupport,
|
|
5
|
+
OperatorValueMap,
|
|
6
|
+
QueryOperator,
|
|
7
|
+
VectorFilter,
|
|
8
|
+
} from '@mastra/core/vector/filter';
|
|
2
9
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
3
10
|
|
|
11
|
+
type OpenSearchOperatorValueMap = Omit<OperatorValueMap, '$options' | '$nor' | '$elemMatch'>;
|
|
12
|
+
|
|
13
|
+
type OpenSearchLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
14
|
+
|
|
15
|
+
type OpenSearchBlacklisted = BlacklistedRootOperators | '$nor';
|
|
16
|
+
|
|
17
|
+
export type OpenSearchVectorFilter = VectorFilter<
|
|
18
|
+
keyof OpenSearchOperatorValueMap,
|
|
19
|
+
OpenSearchOperatorValueMap,
|
|
20
|
+
OpenSearchLogicalOperatorValueMap,
|
|
21
|
+
OpenSearchBlacklisted
|
|
22
|
+
>;
|
|
4
23
|
/**
|
|
5
24
|
* Translator for OpenSearch filter queries.
|
|
6
25
|
* Maintains OpenSearch-compatible syntax while ensuring proper validation
|
|
7
26
|
* and normalization of values.
|
|
8
27
|
*/
|
|
9
|
-
export class OpenSearchFilterTranslator extends BaseFilterTranslator {
|
|
28
|
+
export class OpenSearchFilterTranslator extends BaseFilterTranslator<OpenSearchVectorFilter> {
|
|
10
29
|
protected override getSupportedOperators(): OperatorSupport {
|
|
11
30
|
return {
|
|
12
31
|
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
13
32
|
logical: ['$and', '$or', '$not'],
|
|
14
33
|
array: ['$in', '$nin', '$all'],
|
|
15
|
-
element: ['$exists'],
|
|
16
34
|
regex: ['$regex'],
|
|
17
35
|
custom: [],
|
|
18
36
|
};
|
|
19
37
|
}
|
|
20
38
|
|
|
21
|
-
translate(filter?:
|
|
39
|
+
translate(filter?: OpenSearchVectorFilter): OpenSearchVectorFilter {
|
|
22
40
|
if (this.isEmpty(filter)) return undefined;
|
|
23
41
|
this.validateFilter(filter);
|
|
24
42
|
return this.translateNode(filter);
|
|
25
43
|
}
|
|
26
44
|
|
|
27
|
-
private translateNode(node:
|
|
45
|
+
private translateNode(node: OpenSearchVectorFilter): any {
|
|
28
46
|
// Handle primitive values and arrays
|
|
29
47
|
if (this.isPrimitive(node) || Array.isArray(node)) {
|
|
30
48
|
return node;
|
package/src/vector/index.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// To setup a Opensearch server, run the docker compose file in the opensearch directory
|
|
2
|
-
import type { QueryResult
|
|
2
|
+
import type { QueryResult } from '@mastra/core';
|
|
3
3
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { OpenSearchVector } from './index';
|
|
@@ -215,12 +215,11 @@ describe('OpenSearchVector', () => {
|
|
|
215
215
|
|
|
216
216
|
await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata });
|
|
217
217
|
|
|
218
|
-
const
|
|
218
|
+
const results = await vectorDB.query({
|
|
219
219
|
indexName: testIndexName,
|
|
220
220
|
queryVector: [1.0, 0.1, 0.1],
|
|
221
221
|
topK: 3,
|
|
222
|
-
};
|
|
223
|
-
const results = await vectorDB.query(queryParams);
|
|
222
|
+
});
|
|
224
223
|
|
|
225
224
|
expect(results).toHaveLength(3);
|
|
226
225
|
expect(results[0]?.score).toBeGreaterThan(0);
|
|
@@ -1197,7 +1196,7 @@ describe('OpenSearchVector', () => {
|
|
|
1197
1196
|
vectorDB.query({
|
|
1198
1197
|
indexName,
|
|
1199
1198
|
queryVector: [1, 0, 0],
|
|
1200
|
-
filter: { price: { $invalid: 100 } },
|
|
1199
|
+
filter: { price: { $invalid: 100 } as any },
|
|
1201
1200
|
}),
|
|
1202
1201
|
).rejects.toThrow('Unsupported operator: $invalid');
|
|
1203
1202
|
});
|