@mastra/astra 0.10.2 → 0.10.3-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 +20 -5
- package/dist/_tsup-dts-rollup.d.ts +20 -5
- package/dist/index.cjs +146 -44
- package/dist/index.js +144 -42
- package/package.json +7 -7
- package/src/vector/filter.test.ts +38 -49
- package/src/vector/filter.ts +26 -4
- package/src/vector/index.test.ts +9 -17
- package/src/vector/index.ts +154 -51
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DataAPIClient, UUID } from '@datastax/astra-db-ts';
|
|
2
|
+
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
3
|
import { MastraVector } from '@mastra/core/vector';
|
|
3
4
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
4
5
|
|
|
@@ -68,15 +69,32 @@ var AstraVector = class extends MastraVector {
|
|
|
68
69
|
*/
|
|
69
70
|
async createIndex({ indexName, dimension, metric = "cosine" }) {
|
|
70
71
|
if (!Number.isInteger(dimension) || dimension <= 0) {
|
|
71
|
-
throw new
|
|
72
|
+
throw new MastraError({
|
|
73
|
+
id: "ASTRA_VECTOR_CREATE_INDEX_INVALID_DIMENSION",
|
|
74
|
+
text: "Dimension must be a positive integer",
|
|
75
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
76
|
+
category: ErrorCategory.USER
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await this.#db.createCollection(indexName, {
|
|
81
|
+
vector: {
|
|
82
|
+
dimension,
|
|
83
|
+
metric: metricMap[metric]
|
|
84
|
+
},
|
|
85
|
+
checkExists: false
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
new MastraError(
|
|
89
|
+
{
|
|
90
|
+
id: "ASTRA_VECTOR_CREATE_INDEX_DB_ERROR",
|
|
91
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
92
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
93
|
+
details: { indexName }
|
|
94
|
+
},
|
|
95
|
+
error
|
|
96
|
+
);
|
|
72
97
|
}
|
|
73
|
-
await this.#db.createCollection(indexName, {
|
|
74
|
-
vector: {
|
|
75
|
-
dimension,
|
|
76
|
-
metric: metricMap[metric]
|
|
77
|
-
},
|
|
78
|
-
checkExists: false
|
|
79
|
-
});
|
|
80
98
|
}
|
|
81
99
|
/**
|
|
82
100
|
* Inserts or updates vectors in the specified collection.
|
|
@@ -95,7 +113,19 @@ var AstraVector = class extends MastraVector {
|
|
|
95
113
|
$vector: vector,
|
|
96
114
|
metadata: metadata?.[i] || {}
|
|
97
115
|
}));
|
|
98
|
-
|
|
116
|
+
try {
|
|
117
|
+
await collection.insertMany(records);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw new MastraError(
|
|
120
|
+
{
|
|
121
|
+
id: "ASTRA_VECTOR_UPSERT_DB_ERROR",
|
|
122
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
123
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
124
|
+
details: { indexName }
|
|
125
|
+
},
|
|
126
|
+
error
|
|
127
|
+
);
|
|
128
|
+
}
|
|
99
129
|
return vectorIds;
|
|
100
130
|
}
|
|
101
131
|
transformFilter(filter) {
|
|
@@ -121,29 +151,52 @@ var AstraVector = class extends MastraVector {
|
|
|
121
151
|
}) {
|
|
122
152
|
const collection = this.#db.collection(indexName);
|
|
123
153
|
const translatedFilter = this.transformFilter(filter);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
try {
|
|
155
|
+
const cursor = collection.find(translatedFilter ?? {}, {
|
|
156
|
+
sort: { $vector: queryVector },
|
|
157
|
+
limit: topK,
|
|
158
|
+
includeSimilarity: true,
|
|
159
|
+
projection: {
|
|
160
|
+
$vector: includeVector ? true : false
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
const results = await cursor.toArray();
|
|
164
|
+
return results.map((result) => ({
|
|
165
|
+
id: result.id,
|
|
166
|
+
score: result.$similarity,
|
|
167
|
+
metadata: result.metadata,
|
|
168
|
+
...includeVector && { vector: result.$vector }
|
|
169
|
+
}));
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw new MastraError(
|
|
172
|
+
{
|
|
173
|
+
id: "ASTRA_VECTOR_QUERY_DB_ERROR",
|
|
174
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
175
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
176
|
+
details: { indexName }
|
|
177
|
+
},
|
|
178
|
+
error
|
|
179
|
+
);
|
|
180
|
+
}
|
|
139
181
|
}
|
|
140
182
|
/**
|
|
141
183
|
* Lists all collections in the database.
|
|
142
184
|
*
|
|
143
185
|
* @returns {Promise<string[]>} A promise that resolves to an array of collection names.
|
|
144
186
|
*/
|
|
145
|
-
listIndexes() {
|
|
146
|
-
|
|
187
|
+
async listIndexes() {
|
|
188
|
+
try {
|
|
189
|
+
return await this.#db.listCollections({ nameOnly: true });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw new MastraError(
|
|
192
|
+
{
|
|
193
|
+
id: "ASTRA_VECTOR_LIST_INDEXES_DB_ERROR",
|
|
194
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
195
|
+
category: ErrorCategory.THIRD_PARTY
|
|
196
|
+
},
|
|
197
|
+
error
|
|
198
|
+
);
|
|
199
|
+
}
|
|
147
200
|
}
|
|
148
201
|
/**
|
|
149
202
|
* Retrieves statistics about a vector index.
|
|
@@ -153,16 +206,29 @@ var AstraVector = class extends MastraVector {
|
|
|
153
206
|
*/
|
|
154
207
|
async describeIndex({ indexName }) {
|
|
155
208
|
const collection = this.#db.collection(indexName);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
209
|
+
try {
|
|
210
|
+
const optionsPromise = collection.options();
|
|
211
|
+
const countPromise = collection.countDocuments({}, 100);
|
|
212
|
+
const [options, count] = await Promise.all([optionsPromise, countPromise]);
|
|
213
|
+
const keys = Object.keys(metricMap);
|
|
214
|
+
const metric = keys.find((key) => metricMap[key] === options.vector?.metric);
|
|
215
|
+
return {
|
|
216
|
+
dimension: options.vector?.dimension,
|
|
217
|
+
metric,
|
|
218
|
+
count
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (error instanceof MastraError) throw error;
|
|
222
|
+
throw new MastraError(
|
|
223
|
+
{
|
|
224
|
+
id: "ASTRA_VECTOR_DESCRIBE_INDEX_DB_ERROR",
|
|
225
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
226
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
227
|
+
details: { indexName }
|
|
228
|
+
},
|
|
229
|
+
error
|
|
230
|
+
);
|
|
231
|
+
}
|
|
166
232
|
}
|
|
167
233
|
/**
|
|
168
234
|
* Deletes the specified collection.
|
|
@@ -172,7 +238,19 @@ var AstraVector = class extends MastraVector {
|
|
|
172
238
|
*/
|
|
173
239
|
async deleteIndex({ indexName }) {
|
|
174
240
|
const collection = this.#db.collection(indexName);
|
|
175
|
-
|
|
241
|
+
try {
|
|
242
|
+
await collection.drop();
|
|
243
|
+
} catch (error) {
|
|
244
|
+
throw new MastraError(
|
|
245
|
+
{
|
|
246
|
+
id: "ASTRA_VECTOR_DELETE_INDEX_DB_ERROR",
|
|
247
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
248
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
249
|
+
details: { indexName }
|
|
250
|
+
},
|
|
251
|
+
error
|
|
252
|
+
);
|
|
253
|
+
}
|
|
176
254
|
}
|
|
177
255
|
/**
|
|
178
256
|
* Updates a vector by its ID with the provided vector and/or metadata.
|
|
@@ -185,10 +263,16 @@ var AstraVector = class extends MastraVector {
|
|
|
185
263
|
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
186
264
|
*/
|
|
187
265
|
async updateVector({ indexName, id, update }) {
|
|
266
|
+
if (!update.vector && !update.metadata) {
|
|
267
|
+
throw new MastraError({
|
|
268
|
+
id: "ASTRA_VECTOR_UPDATE_NO_PAYLOAD",
|
|
269
|
+
text: "No updates provided for vector",
|
|
270
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
271
|
+
category: ErrorCategory.USER,
|
|
272
|
+
details: { indexName, id }
|
|
273
|
+
});
|
|
274
|
+
}
|
|
188
275
|
try {
|
|
189
|
-
if (!update.vector && !update.metadata) {
|
|
190
|
-
throw new Error("No updates provided");
|
|
191
|
-
}
|
|
192
276
|
const collection = this.#db.collection(indexName);
|
|
193
277
|
const updateDoc = {};
|
|
194
278
|
if (update.vector) {
|
|
@@ -199,7 +283,16 @@ var AstraVector = class extends MastraVector {
|
|
|
199
283
|
}
|
|
200
284
|
await collection.findOneAndUpdate({ id }, { $set: updateDoc });
|
|
201
285
|
} catch (error) {
|
|
202
|
-
|
|
286
|
+
if (error instanceof MastraError) throw error;
|
|
287
|
+
throw new MastraError(
|
|
288
|
+
{
|
|
289
|
+
id: "ASTRA_VECTOR_UPDATE_FAILED_UNHANDLED",
|
|
290
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
291
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
292
|
+
details: { indexName, id }
|
|
293
|
+
},
|
|
294
|
+
error
|
|
295
|
+
);
|
|
203
296
|
}
|
|
204
297
|
}
|
|
205
298
|
/**
|
|
@@ -214,7 +307,16 @@ var AstraVector = class extends MastraVector {
|
|
|
214
307
|
const collection = this.#db.collection(indexName);
|
|
215
308
|
await collection.deleteOne({ id });
|
|
216
309
|
} catch (error) {
|
|
217
|
-
|
|
310
|
+
if (error instanceof MastraError) throw error;
|
|
311
|
+
throw new MastraError(
|
|
312
|
+
{
|
|
313
|
+
id: "ASTRA_VECTOR_DELETE_FAILED",
|
|
314
|
+
domain: ErrorDomain.MASTRA_VECTOR,
|
|
315
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
316
|
+
details: { indexName, id }
|
|
317
|
+
},
|
|
318
|
+
error
|
|
319
|
+
);
|
|
218
320
|
}
|
|
219
321
|
}
|
|
220
322
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/astra",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3-alpha.1",
|
|
4
4
|
"description": "Astra DB provider for Mastra - includes vector store capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@microsoft/api-extractor": "^7.52.8",
|
|
27
|
-
"@types/node": "^20.
|
|
28
|
-
"eslint": "^9.
|
|
27
|
+
"@types/node": "^20.19.0",
|
|
28
|
+
"eslint": "^9.29.0",
|
|
29
29
|
"tsup": "^8.5.0",
|
|
30
|
-
"typescript": "^5.8.
|
|
31
|
-
"vitest": "^3.2.
|
|
32
|
-
"@
|
|
33
|
-
"@
|
|
30
|
+
"typescript": "^5.8.3",
|
|
31
|
+
"vitest": "^3.2.3",
|
|
32
|
+
"@mastra/core": "0.10.7-alpha.2",
|
|
33
|
+
"@internal/lint": "0.0.13"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@mastra/core": ">=0.10.4-0 <0.11.0"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import type { AstraVectorFilter } from './filter';
|
|
3
4
|
import { AstraFilterTranslator } from './filter';
|
|
4
5
|
|
|
5
6
|
describe('AstraFilterTranslator', () => {
|
|
@@ -12,12 +13,12 @@ describe('AstraFilterTranslator', () => {
|
|
|
12
13
|
// Basic Filter Operations
|
|
13
14
|
describe('basic operations', () => {
|
|
14
15
|
it('handles simple equality', () => {
|
|
15
|
-
const filter = { field: 'value' };
|
|
16
|
+
const filter: AstraVectorFilter = { field: 'value' };
|
|
16
17
|
expect(translator.translate(filter)).toEqual(filter);
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
it('handles comparison operators', () => {
|
|
20
|
-
const filter = {
|
|
21
|
+
const filter: AstraVectorFilter = {
|
|
21
22
|
age: { $gt: 25 },
|
|
22
23
|
score: { $lte: 100 },
|
|
23
24
|
};
|
|
@@ -25,7 +26,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
it('handles valid multiple operators on same field', () => {
|
|
28
|
-
const filter = {
|
|
29
|
+
const filter: AstraVectorFilter = {
|
|
29
30
|
price: { $gt: 100, $lt: 200 },
|
|
30
31
|
quantity: { $gte: 10, $lte: 20 },
|
|
31
32
|
};
|
|
@@ -33,7 +34,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
it('handles null values correctly', () => {
|
|
36
|
-
const filter = {
|
|
37
|
+
const filter: AstraVectorFilter = {
|
|
37
38
|
field: null,
|
|
38
39
|
other: { $eq: null },
|
|
39
40
|
};
|
|
@@ -41,7 +42,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
it('throws on unsupported combinations', () => {
|
|
44
|
-
const filter = {
|
|
45
|
+
const filter: AstraVectorFilter = {
|
|
45
46
|
field: { $gt: 100, $lt: 200 },
|
|
46
47
|
};
|
|
47
48
|
expect(translator.translate(filter)).toEqual(filter);
|
|
@@ -51,14 +52,14 @@ describe('AstraFilterTranslator', () => {
|
|
|
51
52
|
// Array Operations
|
|
52
53
|
describe('array operations', () => {
|
|
53
54
|
it('handles array operators', () => {
|
|
54
|
-
const filter = {
|
|
55
|
+
const filter: AstraVectorFilter = {
|
|
55
56
|
tags: { $all: ['tag1', 'tag2'] },
|
|
56
57
|
categories: { $in: ['A', 'B'] },
|
|
57
58
|
};
|
|
58
59
|
expect(translator.translate(filter)).toEqual(filter);
|
|
59
60
|
});
|
|
60
61
|
it('handles empty array values', () => {
|
|
61
|
-
const filter = {
|
|
62
|
+
const filter: AstraVectorFilter = {
|
|
62
63
|
tags: { $in: [] },
|
|
63
64
|
categories: { $all: [] },
|
|
64
65
|
};
|
|
@@ -66,7 +67,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
66
67
|
});
|
|
67
68
|
|
|
68
69
|
it('handles nested array operators', () => {
|
|
69
|
-
const filter = {
|
|
70
|
+
const filter: AstraVectorFilter = {
|
|
70
71
|
$and: [{ tags: { $all: ['tag1', 'tag2'] } }, { 'nested.array': { $in: [1, 2, 3] } }],
|
|
71
72
|
};
|
|
72
73
|
expect(translator.translate(filter)).toEqual(filter);
|
|
@@ -76,14 +77,14 @@ describe('AstraFilterTranslator', () => {
|
|
|
76
77
|
// Logical Operators
|
|
77
78
|
describe('logical operators', () => {
|
|
78
79
|
it('handles logical operators', () => {
|
|
79
|
-
const filter = {
|
|
80
|
+
const filter: AstraVectorFilter = {
|
|
80
81
|
$or: [{ status: 'active' }, { age: { $gt: 25 } }],
|
|
81
82
|
};
|
|
82
83
|
expect(translator.translate(filter)).toEqual(filter);
|
|
83
84
|
});
|
|
84
85
|
|
|
85
86
|
it('handles nested logical operators', () => {
|
|
86
|
-
const filter = {
|
|
87
|
+
const filter: AstraVectorFilter = {
|
|
87
88
|
$and: [
|
|
88
89
|
{ status: 'active' },
|
|
89
90
|
{ $or: [{ category: { $in: ['A', 'B'] } }, { $and: [{ price: { $gt: 100 } }, { stock: { $lt: 50 } }] }] },
|
|
@@ -93,7 +94,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
it('handles empty conditions in logical operators', () => {
|
|
96
|
-
const filter = {
|
|
97
|
+
const filter: AstraVectorFilter = {
|
|
97
98
|
$and: [],
|
|
98
99
|
$or: [{}],
|
|
99
100
|
field: 'value',
|
|
@@ -147,7 +148,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
147
148
|
|
|
148
149
|
expect(() =>
|
|
149
150
|
translator.translate({
|
|
150
|
-
$or: [{ $in: ['value1', 'value2'] }],
|
|
151
|
+
$or: [{ $in: ['value1', 'value2'] as any }],
|
|
151
152
|
}),
|
|
152
153
|
).toThrow(/Logical operators must contain field conditions/);
|
|
153
154
|
});
|
|
@@ -158,7 +159,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
158
159
|
field: {
|
|
159
160
|
$gt: {
|
|
160
161
|
$or: [{ subfield: 'value1' }, { subfield: 'value2' }],
|
|
161
|
-
},
|
|
162
|
+
} as any,
|
|
162
163
|
},
|
|
163
164
|
}),
|
|
164
165
|
).toThrow();
|
|
@@ -169,7 +170,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
169
170
|
$in: [
|
|
170
171
|
{
|
|
171
172
|
$and: [{ subfield: 'value1' }, { subfield: 'value2' }],
|
|
172
|
-
},
|
|
173
|
+
} as any,
|
|
173
174
|
],
|
|
174
175
|
},
|
|
175
176
|
}),
|
|
@@ -182,7 +183,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
182
183
|
field: {
|
|
183
184
|
$gt: {
|
|
184
185
|
$or: [{ subfield: 'value1' }, { subfield: 'value2' }],
|
|
185
|
-
},
|
|
186
|
+
} as any,
|
|
186
187
|
},
|
|
187
188
|
}),
|
|
188
189
|
).toThrow();
|
|
@@ -200,7 +201,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
200
201
|
|
|
201
202
|
it('throws error for $not if not an object', () => {
|
|
202
203
|
expect(() => translator.translate({ $not: 'value' })).toThrow();
|
|
203
|
-
expect(() => translator.translate({ $not: [{ field: 'value' }] })).toThrow();
|
|
204
|
+
expect(() => translator.translate({ $not: [{ field: 'value' }] } as any)).toThrow();
|
|
204
205
|
});
|
|
205
206
|
|
|
206
207
|
it('throws error for $not if empty', () => {
|
|
@@ -211,7 +212,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
211
212
|
// Nested Objects and Fields
|
|
212
213
|
describe('nested objects and fields', () => {
|
|
213
214
|
it('handles nested objects', () => {
|
|
214
|
-
const filter = {
|
|
215
|
+
const filter: AstraVectorFilter = {
|
|
215
216
|
'user.profile.age': { $gt: 25 },
|
|
216
217
|
'user.status': 'active',
|
|
217
218
|
};
|
|
@@ -219,7 +220,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
219
220
|
});
|
|
220
221
|
|
|
221
222
|
it('handles deeply nested field paths', () => {
|
|
222
|
-
const filter = {
|
|
223
|
+
const filter: AstraVectorFilter = {
|
|
223
224
|
'user.profile.address.city': { $eq: 'New York' },
|
|
224
225
|
'deep.nested.field': { $gt: 100 },
|
|
225
226
|
};
|
|
@@ -227,38 +228,28 @@ describe('AstraFilterTranslator', () => {
|
|
|
227
228
|
});
|
|
228
229
|
|
|
229
230
|
it('preserves nested empty objects', () => {
|
|
230
|
-
const filter = {
|
|
231
|
+
const filter: AstraVectorFilter = {
|
|
231
232
|
status: 'active',
|
|
232
233
|
metadata: {},
|
|
233
|
-
user: {
|
|
234
|
-
|
|
235
|
-
settings: { theme: null },
|
|
236
|
-
},
|
|
234
|
+
'user.profile': {},
|
|
235
|
+
'usersettings.theme': null,
|
|
237
236
|
};
|
|
238
237
|
expect(translator.translate(filter)).toEqual(filter);
|
|
239
238
|
});
|
|
240
239
|
|
|
241
240
|
it('handles mix of operators and empty objects', () => {
|
|
242
|
-
const filter = {
|
|
241
|
+
const filter: AstraVectorFilter = {
|
|
243
242
|
tags: { $in: ['a', 'b'] },
|
|
244
243
|
metadata: {},
|
|
245
|
-
nested: {
|
|
246
|
-
|
|
247
|
-
empty: {},
|
|
248
|
-
},
|
|
244
|
+
'nested.field': { $eq: 'value' },
|
|
245
|
+
'nested.empty': {},
|
|
249
246
|
};
|
|
250
247
|
expect(translator.translate(filter)).toEqual(filter);
|
|
251
248
|
});
|
|
252
249
|
|
|
253
250
|
it('handles deeply nested operators', () => {
|
|
254
|
-
const filter = {
|
|
255
|
-
user: {
|
|
256
|
-
profile: {
|
|
257
|
-
preferences: {
|
|
258
|
-
theme: { $in: ['dark', 'light'] },
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
},
|
|
251
|
+
const filter: AstraVectorFilter = {
|
|
252
|
+
'user.profile.preferences.theme': { $in: ['dark', 'light'] },
|
|
262
253
|
};
|
|
263
254
|
expect(translator.translate(filter)).toEqual(filter);
|
|
264
255
|
});
|
|
@@ -268,13 +259,13 @@ describe('AstraFilterTranslator', () => {
|
|
|
268
259
|
describe('special cases', () => {
|
|
269
260
|
it('handles empty filters', () => {
|
|
270
261
|
expect(translator.translate({})).toEqual({});
|
|
271
|
-
expect(translator.translate(null
|
|
272
|
-
expect(translator.translate(undefined
|
|
262
|
+
expect(translator.translate(null)).toEqual(null);
|
|
263
|
+
expect(translator.translate(undefined)).toEqual(undefined);
|
|
273
264
|
});
|
|
274
265
|
|
|
275
266
|
it('normalizes dates', () => {
|
|
276
267
|
const date = new Date('2024-01-01');
|
|
277
|
-
const filter = { timestamp: { $gt: date } };
|
|
268
|
+
const filter: AstraVectorFilter = { timestamp: { $gt: date } };
|
|
278
269
|
expect(translator.translate(filter)).toEqual({
|
|
279
270
|
timestamp: { $gt: date.toISOString() },
|
|
280
271
|
});
|
|
@@ -293,7 +284,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
293
284
|
|
|
294
285
|
describe('operator validation', () => {
|
|
295
286
|
it('ensure all operator filters are supported', () => {
|
|
296
|
-
const supportedFilters = [
|
|
287
|
+
const supportedFilters: AstraVectorFilter[] = [
|
|
297
288
|
// Basic comparison operators
|
|
298
289
|
{ field: { $eq: 'value' } },
|
|
299
290
|
{ field: { $ne: 'value' } },
|
|
@@ -313,11 +304,9 @@ describe('AstraFilterTranslator', () => {
|
|
|
313
304
|
{ $and: [{ field1: 'value1' }, { field2: 'value2' }] },
|
|
314
305
|
{ $or: [{ field1: 'value1' }, { field2: 'value2' }] },
|
|
315
306
|
|
|
316
|
-
{ $and: { field: 'value' } },
|
|
317
|
-
{ $or: { field: 'value' } },
|
|
318
307
|
{ $not: { field: 'value' } },
|
|
319
308
|
|
|
320
|
-
{ $or: [{ $and: { field1: 'value1' } }, { $not: { field2: 'value2' } }] },
|
|
309
|
+
{ $or: [{ $and: [{ field1: 'value1' }] }, { $not: { field2: 'value2' } }] },
|
|
321
310
|
|
|
322
311
|
{ field: { $not: { $eq: 'value' } } },
|
|
323
312
|
{ field: { $not: { $in: ['value1', 'value2'] } } },
|
|
@@ -333,16 +322,16 @@ describe('AstraFilterTranslator', () => {
|
|
|
333
322
|
});
|
|
334
323
|
|
|
335
324
|
it('throws on unsupported operators', () => {
|
|
336
|
-
expect(() => translator.translate({ field: { $regex: 'value' } })).toThrow('Unsupported operator: $regex');
|
|
337
|
-
const filter = { field: /pattern/i };
|
|
325
|
+
expect(() => translator.translate({ field: { $regex: 'value' } as any })).toThrow('Unsupported operator: $regex');
|
|
326
|
+
const filter: any = { field: /pattern/i };
|
|
338
327
|
expect(() => translator.translate(filter)).toThrow();
|
|
339
|
-
expect(() => translator.translate({ $nor: [{ field: 'value' }] })).toThrow('Unsupported operator: $nor');
|
|
340
|
-
expect(() => translator.translate({ field: { $elemMatch: { $gt: 5 } } })).toThrow(
|
|
328
|
+
expect(() => translator.translate({ $nor: [{ field: 'value' }] } as any)).toThrow('Unsupported operator: $nor');
|
|
329
|
+
expect(() => translator.translate({ field: { $elemMatch: { $gt: 5 } } } as any)).toThrow(
|
|
341
330
|
'Unsupported operator: $elemMatch',
|
|
342
331
|
);
|
|
343
332
|
});
|
|
344
333
|
it('throws error for non-logical operators at top level', () => {
|
|
345
|
-
const invalidFilters = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $exists: true }];
|
|
334
|
+
const invalidFilters: any = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $exists: true }];
|
|
346
335
|
|
|
347
336
|
invalidFilters.forEach(filter => {
|
|
348
337
|
expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
|
|
@@ -350,7 +339,7 @@ describe('AstraFilterTranslator', () => {
|
|
|
350
339
|
});
|
|
351
340
|
|
|
352
341
|
it('allows logical operators at top level', () => {
|
|
353
|
-
const validFilters = [
|
|
342
|
+
const validFilters: AstraVectorFilter[] = [
|
|
354
343
|
{ $and: [{ field: 'value' }] },
|
|
355
344
|
{ $or: [{ field: 'value' }] },
|
|
356
345
|
{ $not: { field: 'value' } },
|
package/src/vector/filter.ts
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import { BaseFilterTranslator } from '@mastra/core/vector/filter';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
VectorFilter,
|
|
4
|
+
OperatorSupport,
|
|
5
|
+
QueryOperator,
|
|
6
|
+
OperatorValueMap,
|
|
7
|
+
LogicalOperatorValueMap,
|
|
8
|
+
BlacklistedRootOperators,
|
|
9
|
+
} from '@mastra/core/vector/filter';
|
|
10
|
+
|
|
11
|
+
type AstraOperatorValueMap = Omit<OperatorValueMap, '$elemMatch' | '$regex' | '$options'> & {
|
|
12
|
+
$size: number; // Astra-specific
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type AstraLogicalOperatorValueMap = Omit<LogicalOperatorValueMap, '$nor'>;
|
|
16
|
+
|
|
17
|
+
type AstraBlacklisted = BlacklistedRootOperators | '$nor' | '$size';
|
|
18
|
+
|
|
19
|
+
export type AstraVectorFilter = VectorFilter<
|
|
20
|
+
keyof AstraOperatorValueMap,
|
|
21
|
+
AstraOperatorValueMap,
|
|
22
|
+
AstraLogicalOperatorValueMap,
|
|
23
|
+
AstraBlacklisted
|
|
24
|
+
>;
|
|
3
25
|
|
|
4
26
|
/**
|
|
5
27
|
* Translator for Astra DB filter queries.
|
|
6
28
|
* Maintains MongoDB-compatible syntax while ensuring proper validation
|
|
7
29
|
* and normalization of values.
|
|
8
30
|
*/
|
|
9
|
-
export class AstraFilterTranslator extends BaseFilterTranslator {
|
|
31
|
+
export class AstraFilterTranslator extends BaseFilterTranslator<AstraVectorFilter> {
|
|
10
32
|
protected override getSupportedOperators(): OperatorSupport {
|
|
11
33
|
return {
|
|
12
34
|
...BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
@@ -17,14 +39,14 @@ export class AstraFilterTranslator extends BaseFilterTranslator {
|
|
|
17
39
|
};
|
|
18
40
|
}
|
|
19
41
|
|
|
20
|
-
translate(filter?:
|
|
42
|
+
translate(filter?: AstraVectorFilter): AstraVectorFilter {
|
|
21
43
|
if (this.isEmpty(filter)) return filter;
|
|
22
44
|
this.validateFilter(filter);
|
|
23
45
|
|
|
24
46
|
return this.translateNode(filter);
|
|
25
47
|
}
|
|
26
48
|
|
|
27
|
-
private translateNode(node:
|
|
49
|
+
private translateNode(node: AstraVectorFilter): any {
|
|
28
50
|
// Handle primitive values and arrays
|
|
29
51
|
if (this.isRegex(node)) {
|
|
30
52
|
throw new Error('Regex is not supported in Astra DB');
|
package/src/vector/index.test.ts
CHANGED
|
@@ -297,7 +297,7 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
297
297
|
vectorDB.query({
|
|
298
298
|
indexName: testIndexName2,
|
|
299
299
|
queryVector: [1, 0, 0, 0],
|
|
300
|
-
filter: { tags: { $all: 'not-an-array' } },
|
|
300
|
+
filter: { tags: { $all: 'not-an-array' as any } },
|
|
301
301
|
}),
|
|
302
302
|
).rejects.toThrow();
|
|
303
303
|
});
|
|
@@ -307,15 +307,7 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
307
307
|
vectorDB.query({
|
|
308
308
|
indexName: testIndexName2,
|
|
309
309
|
queryVector: [1, 0, 0, 0],
|
|
310
|
-
filter: { tags: { $in: null } },
|
|
311
|
-
}),
|
|
312
|
-
).rejects.toThrow();
|
|
313
|
-
|
|
314
|
-
await expect(
|
|
315
|
-
vectorDB.query({
|
|
316
|
-
indexName: testIndexName2,
|
|
317
|
-
queryVector: [1, 0, 0, 0],
|
|
318
|
-
filter: { tags: { $all: 'not-an-array' } },
|
|
310
|
+
filter: { tags: { $in: null as any } },
|
|
319
311
|
}),
|
|
320
312
|
).rejects.toThrow();
|
|
321
313
|
});
|
|
@@ -327,7 +319,7 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
327
319
|
vectorDB.query({
|
|
328
320
|
indexName: testIndexName2,
|
|
329
321
|
queryVector: [1, 0, 0, 0],
|
|
330
|
-
filter: { field: { $in: val } },
|
|
322
|
+
filter: { field: { $in: val as any } },
|
|
331
323
|
}),
|
|
332
324
|
).rejects.toThrow();
|
|
333
325
|
}
|
|
@@ -340,7 +332,7 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
340
332
|
vectorDB.query({
|
|
341
333
|
indexName: testIndexName2,
|
|
342
334
|
queryVector: [1, 0, 0, 0],
|
|
343
|
-
filter: { field: { $nin: val } },
|
|
335
|
+
filter: { field: { $nin: val as any } },
|
|
344
336
|
}),
|
|
345
337
|
).rejects.toThrow();
|
|
346
338
|
}
|
|
@@ -353,7 +345,7 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
353
345
|
vectorDB.query({
|
|
354
346
|
indexName: testIndexName2,
|
|
355
347
|
queryVector: [1, 0, 0, 0],
|
|
356
|
-
filter: { field: { $all: val } },
|
|
348
|
+
filter: { field: { $all: val as any } },
|
|
357
349
|
}),
|
|
358
350
|
).rejects.toThrow();
|
|
359
351
|
}
|
|
@@ -366,7 +358,7 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
366
358
|
vectorDB.query({
|
|
367
359
|
indexName: testIndexName2,
|
|
368
360
|
queryVector: [1, 0, 0, 0],
|
|
369
|
-
filter: { field: { $exists: val } },
|
|
361
|
+
filter: { field: { $exists: val as any } },
|
|
370
362
|
}),
|
|
371
363
|
).rejects.toThrow();
|
|
372
364
|
}
|
|
@@ -395,9 +387,9 @@ describe.skip('AstraVector Integration Tests', () => {
|
|
|
395
387
|
indexName: testIndexName2,
|
|
396
388
|
queryVector: [1, 0, 0, 0],
|
|
397
389
|
filter: {
|
|
398
|
-
field1: { $in: 'not-array' },
|
|
399
|
-
field2: { $exists: 'not-boolean' },
|
|
400
|
-
field3: { $gt: 'not-number' },
|
|
390
|
+
field1: { $in: 'not-array' as any },
|
|
391
|
+
field2: { $exists: 'not-boolean' as any },
|
|
392
|
+
field3: { $gt: 'not-number' as any },
|
|
401
393
|
},
|
|
402
394
|
}),
|
|
403
395
|
).rejects.toThrow();
|