@mastra/opensearch 0.10.4-alpha.0 → 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 +11 -0
- package/dist/_tsup-dts-rollup.d.cts +18 -5
- package/dist/_tsup-dts-rollup.d.ts +18 -5
- package/dist/index.cjs +1 -2
- package/dist/index.js +1 -2
- package/package.json +3 -3
- 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 +7 -5
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/opensearch@0.10.4-alpha.
|
|
2
|
+
> @mastra/opensearch@0.10.4-alpha.1 build /home/runner/work/mastra/mastra/stores/opensearch
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.5.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 11957ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/opensearch/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.3
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/opensearch/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 11764ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[
|
|
21
|
-
[
|
|
22
|
-
[
|
|
23
|
-
[
|
|
20
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m23.79 KB[39m
|
|
21
|
+
[32mCJS[39m ⚡️ Build success in 807ms
|
|
22
|
+
[32mESM[39m [1mdist/index.js [22m[32m23.58 KB[39m
|
|
23
|
+
[32mESM[39m ⚡️ Build success in 814ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @mastra/opensearch
|
|
2
2
|
|
|
3
|
+
## 0.10.4-alpha.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 144eb0b: [MASTRA-3669] Metadata Filter Types
|
|
8
|
+
- Updated dependencies [15e9d26]
|
|
9
|
+
- Updated dependencies [07d6d88]
|
|
10
|
+
- Updated dependencies [5d74aab]
|
|
11
|
+
- Updated dependencies [144eb0b]
|
|
12
|
+
- @mastra/core@0.10.7-alpha.2
|
|
13
|
+
|
|
3
14
|
## 0.10.4-alpha.0
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
+
import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
|
|
2
3
|
import type { CreateIndexParams } from '@mastra/core';
|
|
3
4
|
import type { DeleteIndexParams } from '@mastra/core';
|
|
4
5
|
import type { DeleteVectorParams } from '@mastra/core';
|
|
5
6
|
import type { DescribeIndexParams } from '@mastra/core';
|
|
6
7
|
import type { IndexStats } from '@mastra/core';
|
|
8
|
+
import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
|
|
7
9
|
import { MastraVector } from '@mastra/core/vector';
|
|
8
10
|
import type { OperatorSupport } from '@mastra/core/vector/filter';
|
|
11
|
+
import type { OperatorValueMap } from '@mastra/core/vector/filter';
|
|
9
12
|
import type { QueryResult } from '@mastra/core';
|
|
10
13
|
import type { QueryVectorParams } from '@mastra/core';
|
|
11
14
|
import type { UpdateVectorParams } from '@mastra/core';
|
|
@@ -18,14 +21,16 @@ import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
|
18
21
|
*/
|
|
19
22
|
export declare const OPENSEARCH_PROMPT = "When querying OpenSearch, you can ONLY use the operators listed below. Any other operators will be rejected.\nImportant: Do not explain how to construct the filter\u2014use the specified operators and fields to search the content and return relevant results.\nIf a user tries to use an unsupported operator, reject the filter entirely and let them know that the operator is not supported.\n\nBasic Comparison Operators:\n- $eq: Exact match (default for field: value)\n Example: { \"category\": \"electronics\" }\n- $ne: Not equal\n Example: { \"category\": { \"$ne\": \"electronics\" } }\n- $gt: Greater than\n Example: { \"price\": { \"$gt\": 100 } }\n- $gte: Greater than or equal\n Example: { \"price\": { \"$gte\": 100 } }\n- $lt: Less than\n Example: { \"price\": { \"$lt\": 100 } }\n- $lte: Less than or equal\n Example: { \"price\": { \"$lte\": 100 } }\n\nArray Operators:\n- $in: Match any value in array\n Example: { \"category\": { \"$in\": [\"electronics\", \"books\"] } }\n- $nin: Does not match any value in array\n Example: { \"category\": { \"$nin\": [\"electronics\", \"books\"] } }\n- $all: Match all values in array\n Example: { \"tags\": { \"$all\": [\"premium\", \"sale\"] } }\n\nLogical Operators:\n- $and: Logical AND (implicit when using multiple conditions)\n Example: { \"$and\": [{ \"price\": { \"$gt\": 100 } }, { \"category\": \"electronics\" }] }\n- $or: Logical OR\n Example: { \"$or\": [{ \"price\": { \"$lt\": 50 } }, { \"category\": \"books\" }] }\n- $not: Logical NOT\n Example: { \"$not\": { \"category\": \"electronics\" } }\n\nElement Operators:\n- $exists: Check if field exists\n Example: { \"rating\": { \"$exists\": true } }\n\nRegex Operator:\n- $regex: Match using a regular expression (ECMAScript syntax)\n Example: { \"name\": { \"$regex\": \"^Sam.*son$\" } }\n Note: Regex queries are supported for string fields only. Use valid ECMAScript patterns; invalid patterns will throw an error.\n\nRestrictions:\n- Nested fields are supported using dot notation (e.g., \"address.city\").\n- Multiple conditions on the same field are supported (e.g., { \"price\": { \"$gte\": 100, \"$lte\": 1000 } }).\n- Only logical operators ($and, $or, $not) can be used at the top level.\n- All other operators must be used within a field condition.\n Valid: { \"field\": { \"$gt\": 100 } }\n Valid: { \"$and\": [...] }\n Invalid: { \"$gt\": 100 }\n- Logical operators must contain field conditions, not direct operators.\n Valid: { \"$and\": [{ \"field\": { \"$gt\": 100 } }] }\n Invalid: { \"$and\": [{ \"$gt\": 100 }] }\n- $not operator:\n - Must be an object\n - Cannot be empty\n - Can be used at field level or top level\n - Valid: { \"$not\": { \"field\": \"value\" } }\n - Valid: { \"field\": { \"$not\": { \"$eq\": \"value\" } } }\n- Array operators work on array fields only.\n- Empty arrays in conditions are handled gracefully.\n- Regex queries are case-sensitive by default; use patterns accordingly.\n\nExample Complex Query:\n{\n \"$and\": [\n { \"category\": { \"$in\": [\"electronics\", \"computers\"] } },\n { \"price\": { \"$gte\": 100, \"$lte\": 1000 } },\n { \"tags\": { \"$all\": [\"premium\"] } },\n { \"rating\": { \"$exists\": true, \"$gt\": 4 } },\n { \"$or\": [\n { \"stock\": { \"$gt\": 0 } },\n { \"preorder\": true }\n ]},\n { \"name\": { \"$regex\": \"^Sam.*son$\" } }\n ]\n}";
|
|
20
23
|
|
|
24
|
+
declare type OpenSearchBlacklisted = BlacklistedRootOperators | '$nor';
|
|
25
|
+
|
|
21
26
|
/**
|
|
22
27
|
* Translator for OpenSearch filter queries.
|
|
23
28
|
* Maintains OpenSearch-compatible syntax while ensuring proper validation
|
|
24
29
|
* and normalization of values.
|
|
25
30
|
*/
|
|
26
|
-
export declare class OpenSearchFilterTranslator extends BaseFilterTranslator {
|
|
31
|
+
export declare class OpenSearchFilterTranslator extends BaseFilterTranslator<OpenSearchVectorFilter> {
|
|
27
32
|
protected getSupportedOperators(): OperatorSupport;
|
|
28
|
-
translate(filter?:
|
|
33
|
+
translate(filter?: OpenSearchVectorFilter): OpenSearchVectorFilter;
|
|
29
34
|
private translateNode;
|
|
30
35
|
/**
|
|
31
36
|
* Handles translation of nested objects with dot notation fields
|
|
@@ -58,7 +63,11 @@ export declare class OpenSearchFilterTranslator extends BaseFilterTranslator {
|
|
|
58
63
|
private createRangeQuery;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
declare
|
|
66
|
+
declare type OpenSearchLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
67
|
+
|
|
68
|
+
declare type OpenSearchOperatorValueMap = Omit<OperatorValueMap, '$options' | '$nor' | '$elemMatch'>;
|
|
69
|
+
|
|
70
|
+
declare class OpenSearchVector extends MastraVector<OpenSearchVectorFilter> {
|
|
62
71
|
private client;
|
|
63
72
|
/**
|
|
64
73
|
* Creates a new OpenSearchVector client.
|
|
@@ -117,7 +126,7 @@ declare class OpenSearchVector extends MastraVector {
|
|
|
117
126
|
* @param {boolean} [includeVectors=false] - Whether to include the vectors in the response.
|
|
118
127
|
* @returns {Promise<QueryResult[]>} A promise that resolves to an array of query results.
|
|
119
128
|
*/
|
|
120
|
-
query({ indexName, queryVector, filter, topK, includeVector, }:
|
|
129
|
+
query({ indexName, queryVector, filter, topK, includeVector, }: OpenSearchVectorParams): Promise<QueryResult[]>;
|
|
121
130
|
/**
|
|
122
131
|
* Validates the dimensions of the vectors.
|
|
123
132
|
*
|
|
@@ -129,7 +138,7 @@ declare class OpenSearchVector extends MastraVector {
|
|
|
129
138
|
/**
|
|
130
139
|
* Transforms the filter to the OpenSearch DSL.
|
|
131
140
|
*
|
|
132
|
-
* @param {
|
|
141
|
+
* @param {OpenSearchVectorFilter} filter - The filter to transform.
|
|
133
142
|
* @returns {Record<string, any>} The transformed filter.
|
|
134
143
|
*/
|
|
135
144
|
private transformFilter;
|
|
@@ -156,4 +165,8 @@ declare class OpenSearchVector extends MastraVector {
|
|
|
156
165
|
export { OpenSearchVector }
|
|
157
166
|
export { OpenSearchVector as OpenSearchVector_alias_1 }
|
|
158
167
|
|
|
168
|
+
export declare type OpenSearchVectorFilter = VectorFilter<keyof OpenSearchOperatorValueMap, OpenSearchOperatorValueMap, OpenSearchLogicalOperatorValueMap, OpenSearchBlacklisted>;
|
|
169
|
+
|
|
170
|
+
declare type OpenSearchVectorParams = QueryVectorParams<OpenSearchVectorFilter>;
|
|
171
|
+
|
|
159
172
|
export { }
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
+
import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
|
|
2
3
|
import type { CreateIndexParams } from '@mastra/core';
|
|
3
4
|
import type { DeleteIndexParams } from '@mastra/core';
|
|
4
5
|
import type { DeleteVectorParams } from '@mastra/core';
|
|
5
6
|
import type { DescribeIndexParams } from '@mastra/core';
|
|
6
7
|
import type { IndexStats } from '@mastra/core';
|
|
8
|
+
import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
|
|
7
9
|
import { MastraVector } from '@mastra/core/vector';
|
|
8
10
|
import type { OperatorSupport } from '@mastra/core/vector/filter';
|
|
11
|
+
import type { OperatorValueMap } from '@mastra/core/vector/filter';
|
|
9
12
|
import type { QueryResult } from '@mastra/core';
|
|
10
13
|
import type { QueryVectorParams } from '@mastra/core';
|
|
11
14
|
import type { UpdateVectorParams } from '@mastra/core';
|
|
@@ -18,14 +21,16 @@ import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
|
18
21
|
*/
|
|
19
22
|
export declare const OPENSEARCH_PROMPT = "When querying OpenSearch, you can ONLY use the operators listed below. Any other operators will be rejected.\nImportant: Do not explain how to construct the filter\u2014use the specified operators and fields to search the content and return relevant results.\nIf a user tries to use an unsupported operator, reject the filter entirely and let them know that the operator is not supported.\n\nBasic Comparison Operators:\n- $eq: Exact match (default for field: value)\n Example: { \"category\": \"electronics\" }\n- $ne: Not equal\n Example: { \"category\": { \"$ne\": \"electronics\" } }\n- $gt: Greater than\n Example: { \"price\": { \"$gt\": 100 } }\n- $gte: Greater than or equal\n Example: { \"price\": { \"$gte\": 100 } }\n- $lt: Less than\n Example: { \"price\": { \"$lt\": 100 } }\n- $lte: Less than or equal\n Example: { \"price\": { \"$lte\": 100 } }\n\nArray Operators:\n- $in: Match any value in array\n Example: { \"category\": { \"$in\": [\"electronics\", \"books\"] } }\n- $nin: Does not match any value in array\n Example: { \"category\": { \"$nin\": [\"electronics\", \"books\"] } }\n- $all: Match all values in array\n Example: { \"tags\": { \"$all\": [\"premium\", \"sale\"] } }\n\nLogical Operators:\n- $and: Logical AND (implicit when using multiple conditions)\n Example: { \"$and\": [{ \"price\": { \"$gt\": 100 } }, { \"category\": \"electronics\" }] }\n- $or: Logical OR\n Example: { \"$or\": [{ \"price\": { \"$lt\": 50 } }, { \"category\": \"books\" }] }\n- $not: Logical NOT\n Example: { \"$not\": { \"category\": \"electronics\" } }\n\nElement Operators:\n- $exists: Check if field exists\n Example: { \"rating\": { \"$exists\": true } }\n\nRegex Operator:\n- $regex: Match using a regular expression (ECMAScript syntax)\n Example: { \"name\": { \"$regex\": \"^Sam.*son$\" } }\n Note: Regex queries are supported for string fields only. Use valid ECMAScript patterns; invalid patterns will throw an error.\n\nRestrictions:\n- Nested fields are supported using dot notation (e.g., \"address.city\").\n- Multiple conditions on the same field are supported (e.g., { \"price\": { \"$gte\": 100, \"$lte\": 1000 } }).\n- Only logical operators ($and, $or, $not) can be used at the top level.\n- All other operators must be used within a field condition.\n Valid: { \"field\": { \"$gt\": 100 } }\n Valid: { \"$and\": [...] }\n Invalid: { \"$gt\": 100 }\n- Logical operators must contain field conditions, not direct operators.\n Valid: { \"$and\": [{ \"field\": { \"$gt\": 100 } }] }\n Invalid: { \"$and\": [{ \"$gt\": 100 }] }\n- $not operator:\n - Must be an object\n - Cannot be empty\n - Can be used at field level or top level\n - Valid: { \"$not\": { \"field\": \"value\" } }\n - Valid: { \"field\": { \"$not\": { \"$eq\": \"value\" } } }\n- Array operators work on array fields only.\n- Empty arrays in conditions are handled gracefully.\n- Regex queries are case-sensitive by default; use patterns accordingly.\n\nExample Complex Query:\n{\n \"$and\": [\n { \"category\": { \"$in\": [\"electronics\", \"computers\"] } },\n { \"price\": { \"$gte\": 100, \"$lte\": 1000 } },\n { \"tags\": { \"$all\": [\"premium\"] } },\n { \"rating\": { \"$exists\": true, \"$gt\": 4 } },\n { \"$or\": [\n { \"stock\": { \"$gt\": 0 } },\n { \"preorder\": true }\n ]},\n { \"name\": { \"$regex\": \"^Sam.*son$\" } }\n ]\n}";
|
|
20
23
|
|
|
24
|
+
declare type OpenSearchBlacklisted = BlacklistedRootOperators | '$nor';
|
|
25
|
+
|
|
21
26
|
/**
|
|
22
27
|
* Translator for OpenSearch filter queries.
|
|
23
28
|
* Maintains OpenSearch-compatible syntax while ensuring proper validation
|
|
24
29
|
* and normalization of values.
|
|
25
30
|
*/
|
|
26
|
-
export declare class OpenSearchFilterTranslator extends BaseFilterTranslator {
|
|
31
|
+
export declare class OpenSearchFilterTranslator extends BaseFilterTranslator<OpenSearchVectorFilter> {
|
|
27
32
|
protected getSupportedOperators(): OperatorSupport;
|
|
28
|
-
translate(filter?:
|
|
33
|
+
translate(filter?: OpenSearchVectorFilter): OpenSearchVectorFilter;
|
|
29
34
|
private translateNode;
|
|
30
35
|
/**
|
|
31
36
|
* Handles translation of nested objects with dot notation fields
|
|
@@ -58,7 +63,11 @@ export declare class OpenSearchFilterTranslator extends BaseFilterTranslator {
|
|
|
58
63
|
private createRangeQuery;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
declare
|
|
66
|
+
declare type OpenSearchLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
67
|
+
|
|
68
|
+
declare type OpenSearchOperatorValueMap = Omit<OperatorValueMap, '$options' | '$nor' | '$elemMatch'>;
|
|
69
|
+
|
|
70
|
+
declare class OpenSearchVector extends MastraVector<OpenSearchVectorFilter> {
|
|
62
71
|
private client;
|
|
63
72
|
/**
|
|
64
73
|
* Creates a new OpenSearchVector client.
|
|
@@ -117,7 +126,7 @@ declare class OpenSearchVector extends MastraVector {
|
|
|
117
126
|
* @param {boolean} [includeVectors=false] - Whether to include the vectors in the response.
|
|
118
127
|
* @returns {Promise<QueryResult[]>} A promise that resolves to an array of query results.
|
|
119
128
|
*/
|
|
120
|
-
query({ indexName, queryVector, filter, topK, includeVector, }:
|
|
129
|
+
query({ indexName, queryVector, filter, topK, includeVector, }: OpenSearchVectorParams): Promise<QueryResult[]>;
|
|
121
130
|
/**
|
|
122
131
|
* Validates the dimensions of the vectors.
|
|
123
132
|
*
|
|
@@ -129,7 +138,7 @@ declare class OpenSearchVector extends MastraVector {
|
|
|
129
138
|
/**
|
|
130
139
|
* Transforms the filter to the OpenSearch DSL.
|
|
131
140
|
*
|
|
132
|
-
* @param {
|
|
141
|
+
* @param {OpenSearchVectorFilter} filter - The filter to transform.
|
|
133
142
|
* @returns {Record<string, any>} The transformed filter.
|
|
134
143
|
*/
|
|
135
144
|
private transformFilter;
|
|
@@ -156,4 +165,8 @@ declare class OpenSearchVector extends MastraVector {
|
|
|
156
165
|
export { OpenSearchVector }
|
|
157
166
|
export { OpenSearchVector as OpenSearchVector_alias_1 }
|
|
158
167
|
|
|
168
|
+
export declare type OpenSearchVectorFilter = VectorFilter<keyof OpenSearchOperatorValueMap, OpenSearchOperatorValueMap, OpenSearchLogicalOperatorValueMap, OpenSearchBlacklisted>;
|
|
169
|
+
|
|
170
|
+
declare type OpenSearchVectorParams = QueryVectorParams<OpenSearchVectorFilter>;
|
|
171
|
+
|
|
159
172
|
export { }
|
package/dist/index.cjs
CHANGED
|
@@ -12,7 +12,6 @@ var OpenSearchFilterTranslator = class extends filter.BaseFilterTranslator {
|
|
|
12
12
|
...filter.BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
13
13
|
logical: ["$and", "$or", "$not"],
|
|
14
14
|
array: ["$in", "$nin", "$all"],
|
|
15
|
-
element: ["$exists"],
|
|
16
15
|
regex: ["$regex"],
|
|
17
16
|
custom: []
|
|
18
17
|
};
|
|
@@ -608,7 +607,7 @@ var OpenSearchVector = class extends vector.MastraVector {
|
|
|
608
607
|
/**
|
|
609
608
|
* Transforms the filter to the OpenSearch DSL.
|
|
610
609
|
*
|
|
611
|
-
* @param {
|
|
610
|
+
* @param {OpenSearchVectorFilter} filter - The filter to transform.
|
|
612
611
|
* @returns {Record<string, any>} The transformed filter.
|
|
613
612
|
*/
|
|
614
613
|
transformFilter(filter) {
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,6 @@ var OpenSearchFilterTranslator = class extends BaseFilterTranslator {
|
|
|
10
10
|
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
11
11
|
logical: ["$and", "$or", "$not"],
|
|
12
12
|
array: ["$in", "$nin", "$all"],
|
|
13
|
-
element: ["$exists"],
|
|
14
13
|
regex: ["$regex"],
|
|
15
14
|
custom: []
|
|
16
15
|
};
|
|
@@ -606,7 +605,7 @@ var OpenSearchVector = class extends MastraVector {
|
|
|
606
605
|
/**
|
|
607
606
|
* Transforms the filter to the OpenSearch DSL.
|
|
608
607
|
*
|
|
609
|
-
* @param {
|
|
608
|
+
* @param {OpenSearchVectorFilter} filter - The filter to transform.
|
|
610
609
|
* @returns {Record<string, any>} The transformed filter.
|
|
611
610
|
*/
|
|
612
611
|
transformFilter(filter) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/opensearch",
|
|
3
|
-
"version": "0.10.4-alpha.
|
|
3
|
+
"version": "0.10.4-alpha.1",
|
|
4
4
|
"description": "OpenSearch vector store provider for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@microsoft/api-extractor": "^7.52.8",
|
|
26
26
|
"@types/node": "^20.19.0",
|
|
27
|
-
"eslint": "^9.
|
|
27
|
+
"eslint": "^9.29.0",
|
|
28
28
|
"tsup": "^8.5.0",
|
|
29
29
|
"typescript": "^5.8.3",
|
|
30
30
|
"vitest": "^3.2.3",
|
|
31
31
|
"@internal/lint": "0.0.13",
|
|
32
|
-
"@mastra/core": "0.10.7-alpha.
|
|
32
|
+
"@mastra/core": "0.10.7-alpha.2"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@mastra/core": ">=0.10.4-0 <0.11.0"
|
|
@@ -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
|
});
|
package/src/vector/index.ts
CHANGED
|
@@ -11,9 +11,9 @@ import type {
|
|
|
11
11
|
} from '@mastra/core';
|
|
12
12
|
import { MastraError, ErrorDomain, ErrorCategory } from '@mastra/core/error';
|
|
13
13
|
import { MastraVector } from '@mastra/core/vector';
|
|
14
|
-
import type { VectorFilter } from '@mastra/core/vector/filter';
|
|
15
14
|
import { Client as OpenSearchClient } from '@opensearch-project/opensearch';
|
|
16
15
|
import { OpenSearchFilterTranslator } from './filter';
|
|
16
|
+
import type { OpenSearchVectorFilter } from './filter';
|
|
17
17
|
|
|
18
18
|
const METRIC_MAPPING = {
|
|
19
19
|
cosine: 'cosinesimil',
|
|
@@ -27,7 +27,9 @@ const REVERSE_METRIC_MAPPING = {
|
|
|
27
27
|
innerproduct: 'dotproduct',
|
|
28
28
|
} as const;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
type OpenSearchVectorParams = QueryVectorParams<OpenSearchVectorFilter>;
|
|
31
|
+
|
|
32
|
+
export class OpenSearchVector extends MastraVector<OpenSearchVectorFilter> {
|
|
31
33
|
private client: OpenSearchClient;
|
|
32
34
|
|
|
33
35
|
/**
|
|
@@ -243,7 +245,7 @@ export class OpenSearchVector extends MastraVector {
|
|
|
243
245
|
filter,
|
|
244
246
|
topK = 10,
|
|
245
247
|
includeVector = false,
|
|
246
|
-
}:
|
|
248
|
+
}: OpenSearchVectorParams): Promise<QueryResult[]> {
|
|
247
249
|
try {
|
|
248
250
|
const translatedFilter = this.transformFilter(filter);
|
|
249
251
|
|
|
@@ -300,10 +302,10 @@ export class OpenSearchVector extends MastraVector {
|
|
|
300
302
|
/**
|
|
301
303
|
* Transforms the filter to the OpenSearch DSL.
|
|
302
304
|
*
|
|
303
|
-
* @param {
|
|
305
|
+
* @param {OpenSearchVectorFilter} filter - The filter to transform.
|
|
304
306
|
* @returns {Record<string, any>} The transformed filter.
|
|
305
307
|
*/
|
|
306
|
-
private transformFilter(filter?:
|
|
308
|
+
private transformFilter(filter?: OpenSearchVectorFilter): any {
|
|
307
309
|
const translator = new OpenSearchFilterTranslator();
|
|
308
310
|
return translator.translate(filter);
|
|
309
311
|
}
|