@mastra/elasticsearch 1.3.0 → 1.3.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/CHANGELOG.md +18 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-rag-vector-databases.md +28 -4
- package/dist/docs/references/reference-vectors-elasticsearch.md +2 -0
- package/dist/index.cjs +840 -1072
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -125
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +127 -125
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +837 -1068
- package/dist/index.js.map +1 -1
- package/package.json +17 -16
package/dist/index.cjs
CHANGED
|
@@ -1,1078 +1,846 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return pattern.replace(/\\/g, "\\\\").replace(/\*/g, "\\*").replace(/\?/g, "\\?");
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Translates regex patterns to ElasticSearch query syntax
|
|
239
|
-
*/
|
|
240
|
-
translateRegexOperator(field, value) {
|
|
241
|
-
const regexValue = typeof value === "string" ? value : value.toString();
|
|
242
|
-
let processedRegex = regexValue;
|
|
243
|
-
const hasStartAnchor = regexValue.startsWith("^");
|
|
244
|
-
const hasEndAnchor = regexValue.endsWith("$");
|
|
245
|
-
if (hasStartAnchor || hasEndAnchor) {
|
|
246
|
-
if (hasStartAnchor) {
|
|
247
|
-
processedRegex = processedRegex.substring(1);
|
|
248
|
-
}
|
|
249
|
-
if (hasEndAnchor) {
|
|
250
|
-
processedRegex = processedRegex.substring(0, processedRegex.length - 1);
|
|
251
|
-
}
|
|
252
|
-
const escapedPattern = this.escapeWildcardMetacharacters(processedRegex);
|
|
253
|
-
let wildcardPattern = escapedPattern;
|
|
254
|
-
if (!hasStartAnchor) {
|
|
255
|
-
wildcardPattern = "*" + wildcardPattern;
|
|
256
|
-
}
|
|
257
|
-
if (!hasEndAnchor) {
|
|
258
|
-
wildcardPattern = wildcardPattern + "*";
|
|
259
|
-
}
|
|
260
|
-
return { wildcard: { [field]: { value: wildcardPattern } } };
|
|
261
|
-
}
|
|
262
|
-
return { regexp: { [field]: { value: regexValue } } };
|
|
263
|
-
}
|
|
264
|
-
addKeywordIfNeeded(field, value) {
|
|
265
|
-
if (typeof value === "string") {
|
|
266
|
-
return `${field}.keyword`;
|
|
267
|
-
}
|
|
268
|
-
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
269
|
-
return `${field}.keyword`;
|
|
270
|
-
}
|
|
271
|
-
return field;
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Helper method to handle special cases for the $not operator
|
|
275
|
-
*/
|
|
276
|
-
handleNotOperatorSpecialCases(value, field) {
|
|
277
|
-
if (value === null) {
|
|
278
|
-
return { exists: { field } };
|
|
279
|
-
}
|
|
280
|
-
if (typeof value === "object" && value !== null) {
|
|
281
|
-
if ("$eq" in value && value.$eq === null) {
|
|
282
|
-
return { exists: { field } };
|
|
283
|
-
}
|
|
284
|
-
if ("$ne" in value && value.$ne === null) {
|
|
285
|
-
return {
|
|
286
|
-
bool: {
|
|
287
|
-
must_not: [{ exists: { field } }]
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
translateOperator(operator, value, field) {
|
|
295
|
-
if (!this.isOperator(operator)) {
|
|
296
|
-
throw new Error(`Unsupported operator: ${operator}`);
|
|
297
|
-
}
|
|
298
|
-
if (operator === "$not" && field) {
|
|
299
|
-
const specialCaseResult = this.handleNotOperatorSpecialCases(value, field);
|
|
300
|
-
if (specialCaseResult) {
|
|
301
|
-
return specialCaseResult;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
if (this.isLogicalOperator(operator)) {
|
|
305
|
-
if (operator === "$not" && field && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
306
|
-
const entries = Object.entries(value);
|
|
307
|
-
if (entries.length > 0) {
|
|
308
|
-
if (entries.every(([op]) => this.isOperator(op))) {
|
|
309
|
-
const translatedCondition = this.translateFieldConditions(field, value);
|
|
310
|
-
return {
|
|
311
|
-
bool: {
|
|
312
|
-
must_not: [translatedCondition]
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
if (entries.length === 1 && entries[0] && this.isOperator(entries[0][0])) {
|
|
317
|
-
const [nestedOp, nestedVal] = entries[0];
|
|
318
|
-
const translatedNested = this.translateFieldOperator(field, nestedOp, nestedVal);
|
|
319
|
-
return {
|
|
320
|
-
bool: {
|
|
321
|
-
must_not: [translatedNested]
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return this.translateLogicalOperator(operator, value);
|
|
328
|
-
}
|
|
329
|
-
if (field) {
|
|
330
|
-
return this.translateFieldOperator(field, operator, value);
|
|
331
|
-
}
|
|
332
|
-
return value;
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Translates field conditions to ElasticSearch query syntax
|
|
336
|
-
* Handles special cases like range queries and multiple operators
|
|
337
|
-
*/
|
|
338
|
-
translateFieldConditions(field, conditions) {
|
|
339
|
-
if (this.canOptimizeToRangeQuery(conditions)) {
|
|
340
|
-
return this.createRangeQuery(field, conditions);
|
|
341
|
-
}
|
|
342
|
-
const queryConditions = [];
|
|
343
|
-
Object.entries(conditions).forEach(([operator, value]) => {
|
|
344
|
-
if (this.isOperator(operator)) {
|
|
345
|
-
queryConditions.push(this.translateOperator(operator, value, field));
|
|
346
|
-
} else {
|
|
347
|
-
const fieldWithKeyword = this.addKeywordIfNeeded(`${field}.${operator}`, value);
|
|
348
|
-
queryConditions.push({ term: { [fieldWithKeyword]: value } });
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
if (queryConditions.length === 1) {
|
|
352
|
-
return queryConditions[0];
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
bool: {
|
|
356
|
-
must: queryConditions
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Checks if conditions can be optimized to a range query
|
|
362
|
-
*/
|
|
363
|
-
canOptimizeToRangeQuery(conditions) {
|
|
364
|
-
return Object.keys(conditions).every((op) => this.isNumericOperator(op)) && Object.keys(conditions).length > 0;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Creates a range query from numeric operators
|
|
368
|
-
*/
|
|
369
|
-
createRangeQuery(field, conditions) {
|
|
370
|
-
const rangeParams = Object.fromEntries(
|
|
371
|
-
Object.entries(conditions).map(([op, val]) => [op.replace("$", ""), this.normalizeComparisonValue(val)])
|
|
372
|
-
);
|
|
373
|
-
return { range: { [field]: rangeParams } };
|
|
374
|
-
}
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _elastic_elasticsearch = require("@elastic/elasticsearch");
|
|
3
|
+
let _mastra_core_error = require("@mastra/core/error");
|
|
4
|
+
let _mastra_core_storage = require("@mastra/core/storage");
|
|
5
|
+
let _mastra_core_vector = require("@mastra/core/vector");
|
|
6
|
+
let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
|
|
7
|
+
//#region package.json
|
|
8
|
+
var version = "1.3.1";
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/vector/filter.ts
|
|
11
|
+
/**
|
|
12
|
+
* Translator for ElasticSearch filter queries.
|
|
13
|
+
* Maintains ElasticSearch-compatible syntax while ensuring proper validation
|
|
14
|
+
* and normalization of values.
|
|
15
|
+
*/
|
|
16
|
+
var ElasticSearchFilterTranslator = class extends _mastra_core_vector_filter.BaseFilterTranslator {
|
|
17
|
+
getSupportedOperators() {
|
|
18
|
+
return {
|
|
19
|
+
..._mastra_core_vector_filter.BaseFilterTranslator.DEFAULT_OPERATORS,
|
|
20
|
+
logical: [
|
|
21
|
+
"$and",
|
|
22
|
+
"$or",
|
|
23
|
+
"$not",
|
|
24
|
+
"$nor"
|
|
25
|
+
],
|
|
26
|
+
array: [
|
|
27
|
+
"$in",
|
|
28
|
+
"$nin",
|
|
29
|
+
"$all"
|
|
30
|
+
],
|
|
31
|
+
regex: ["$regex"],
|
|
32
|
+
custom: []
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
translate(filter) {
|
|
36
|
+
if (this.isEmpty(filter)) return void 0;
|
|
37
|
+
this.validateFilter(filter);
|
|
38
|
+
return this.translateNode(filter);
|
|
39
|
+
}
|
|
40
|
+
translateNode(node) {
|
|
41
|
+
if (this.isPrimitive(node) || Array.isArray(node)) return node;
|
|
42
|
+
const entries = Object.entries(node);
|
|
43
|
+
const logicalOperators = [];
|
|
44
|
+
const fieldConditions = [];
|
|
45
|
+
entries.forEach(([key, value]) => {
|
|
46
|
+
if (this.isLogicalOperator(key)) logicalOperators.push([key, value]);
|
|
47
|
+
else fieldConditions.push([key, value]);
|
|
48
|
+
});
|
|
49
|
+
if (logicalOperators.length === 1 && fieldConditions.length === 0) {
|
|
50
|
+
const [operator, value] = logicalOperators[0];
|
|
51
|
+
if (!Array.isArray(value) && typeof value !== "object") throw new Error(`Invalid logical operator structure: ${operator} must have an array or object value`);
|
|
52
|
+
return this.translateLogicalOperator(operator, value);
|
|
53
|
+
}
|
|
54
|
+
const fieldConditionQueries = fieldConditions.map(([key, value]) => {
|
|
55
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
56
|
+
const hasOperators = Object.keys(value).some((k) => this.isOperator(k));
|
|
57
|
+
const nestedField = `metadata.${key}`;
|
|
58
|
+
return hasOperators ? this.translateFieldConditions(nestedField, value) : this.translateNestedObject(nestedField, value);
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(value)) return { terms: { [this.addKeywordIfNeeded(`metadata.${key}`, value)]: value } };
|
|
61
|
+
return { term: { [this.addKeywordIfNeeded(`metadata.${key}`, value)]: value } };
|
|
62
|
+
});
|
|
63
|
+
if (logicalOperators.length > 0) return { bool: { must: [...logicalOperators.map(([operator, value]) => this.translateOperator(operator, value)), ...fieldConditionQueries] } };
|
|
64
|
+
if (fieldConditionQueries.length > 1) return { bool: { must: fieldConditionQueries } };
|
|
65
|
+
if (fieldConditionQueries.length === 1) return fieldConditionQueries[0];
|
|
66
|
+
return { match_all: {} };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Handles translation of nested objects with dot notation fields
|
|
70
|
+
*/
|
|
71
|
+
translateNestedObject(field, value) {
|
|
72
|
+
return { bool: { must: Object.entries(value).map(([subField, subValue]) => {
|
|
73
|
+
const fullField = `${field}.${subField}`;
|
|
74
|
+
if (this.isOperator(subField)) return this.translateOperator(subField, subValue, field);
|
|
75
|
+
if (typeof subValue === "object" && subValue !== null && !Array.isArray(subValue)) {
|
|
76
|
+
if (Object.keys(subValue).some((k) => this.isOperator(k))) return this.translateFieldConditions(fullField, subValue);
|
|
77
|
+
return this.translateNestedObject(fullField, subValue);
|
|
78
|
+
}
|
|
79
|
+
return { term: { [this.addKeywordIfNeeded(fullField, subValue)]: subValue } };
|
|
80
|
+
}) } };
|
|
81
|
+
}
|
|
82
|
+
translateLogicalOperator(operator, value) {
|
|
83
|
+
const conditions = Array.isArray(value) ? value.map((item) => this.translateNode(item)) : [this.translateNode(value)];
|
|
84
|
+
switch (operator) {
|
|
85
|
+
case "$and":
|
|
86
|
+
if (Array.isArray(value) && value.length === 0) return { match_all: {} };
|
|
87
|
+
return { bool: { must: conditions } };
|
|
88
|
+
case "$or":
|
|
89
|
+
if (Array.isArray(value) && value.length === 0) return { bool: { must_not: [{ match_all: {} }] } };
|
|
90
|
+
return { bool: {
|
|
91
|
+
should: conditions,
|
|
92
|
+
minimum_should_match: 1
|
|
93
|
+
} };
|
|
94
|
+
case "$not":
|
|
95
|
+
case "$nor": return { bool: { must_not: conditions } };
|
|
96
|
+
default: return value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
translateFieldOperator(field, operator, value) {
|
|
100
|
+
if (this.isBasicOperator(operator)) {
|
|
101
|
+
const normalizedValue = this.normalizeComparisonValue(value);
|
|
102
|
+
const fieldWithKeyword = this.addKeywordIfNeeded(field, value);
|
|
103
|
+
switch (operator) {
|
|
104
|
+
case "$eq":
|
|
105
|
+
if (value === null) return { bool: { must_not: [{ exists: { field } }] } };
|
|
106
|
+
return { term: { [fieldWithKeyword]: normalizedValue } };
|
|
107
|
+
case "$ne":
|
|
108
|
+
if (value === null) return { exists: { field } };
|
|
109
|
+
return { bool: { must_not: [{ term: { [fieldWithKeyword]: normalizedValue } }] } };
|
|
110
|
+
default: return { term: { [fieldWithKeyword]: normalizedValue } };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (this.isNumericOperator(operator)) {
|
|
114
|
+
const normalizedValue = this.normalizeComparisonValue(value);
|
|
115
|
+
const rangeOp = operator.replace("$", "");
|
|
116
|
+
return { range: { [field]: { [rangeOp]: normalizedValue } } };
|
|
117
|
+
}
|
|
118
|
+
if (this.isArrayOperator(operator)) {
|
|
119
|
+
if (!Array.isArray(value)) throw new Error(`Invalid array operator value: ${operator} requires an array value`);
|
|
120
|
+
const normalizedValues = this.normalizeArrayValues(value);
|
|
121
|
+
const fieldWithKeyword = this.addKeywordIfNeeded(field, value);
|
|
122
|
+
switch (operator) {
|
|
123
|
+
case "$in": return { terms: { [fieldWithKeyword]: normalizedValues } };
|
|
124
|
+
case "$nin":
|
|
125
|
+
if (normalizedValues.length === 0) return { match_all: {} };
|
|
126
|
+
return { bool: { must_not: [{ terms: { [fieldWithKeyword]: normalizedValues } }] } };
|
|
127
|
+
case "$all":
|
|
128
|
+
if (normalizedValues.length === 0) return { bool: { must_not: [{ match_all: {} }] } };
|
|
129
|
+
return { bool: { must: normalizedValues.map((v) => ({ term: { [fieldWithKeyword]: v } })) } };
|
|
130
|
+
default: return { terms: { [fieldWithKeyword]: normalizedValues } };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (this.isElementOperator(operator)) switch (operator) {
|
|
134
|
+
case "$exists": return value ? { exists: { field } } : { bool: { must_not: [{ exists: { field } }] } };
|
|
135
|
+
default: return { exists: { field } };
|
|
136
|
+
}
|
|
137
|
+
if (this.isRegexOperator(operator)) return this.translateRegexOperator(field, value);
|
|
138
|
+
return { term: { [this.addKeywordIfNeeded(field, value)]: value } };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Escapes wildcard metacharacters (* and ?) for use in wildcard queries.
|
|
142
|
+
* Existing wildcard metacharacters in the pattern are escaped before
|
|
143
|
+
* adding leading/trailing * to prevent semantic changes.
|
|
144
|
+
* First escapes backslashes to avoid ambiguous encoding sequences.
|
|
145
|
+
*/
|
|
146
|
+
escapeWildcardMetacharacters(pattern) {
|
|
147
|
+
return pattern.replace(/\\/g, "\\\\").replace(/\*/g, "\\*").replace(/\?/g, "\\?");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Translates regex patterns to ElasticSearch query syntax
|
|
151
|
+
*/
|
|
152
|
+
translateRegexOperator(field, value) {
|
|
153
|
+
const regexValue = typeof value === "string" ? value : value.toString();
|
|
154
|
+
let processedRegex = regexValue;
|
|
155
|
+
const hasStartAnchor = regexValue.startsWith("^");
|
|
156
|
+
const hasEndAnchor = regexValue.endsWith("$");
|
|
157
|
+
if (hasStartAnchor || hasEndAnchor) {
|
|
158
|
+
if (hasStartAnchor) processedRegex = processedRegex.substring(1);
|
|
159
|
+
if (hasEndAnchor) processedRegex = processedRegex.substring(0, processedRegex.length - 1);
|
|
160
|
+
let wildcardPattern = this.escapeWildcardMetacharacters(processedRegex);
|
|
161
|
+
if (!hasStartAnchor) wildcardPattern = "*" + wildcardPattern;
|
|
162
|
+
if (!hasEndAnchor) wildcardPattern = wildcardPattern + "*";
|
|
163
|
+
return { wildcard: { [field]: { value: wildcardPattern } } };
|
|
164
|
+
}
|
|
165
|
+
return { regexp: { [field]: { value: regexValue } } };
|
|
166
|
+
}
|
|
167
|
+
addKeywordIfNeeded(field, value) {
|
|
168
|
+
if (typeof value === "string") return `${field}.keyword`;
|
|
169
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) return `${field}.keyword`;
|
|
170
|
+
return field;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Helper method to handle special cases for the $not operator
|
|
174
|
+
*/
|
|
175
|
+
handleNotOperatorSpecialCases(value, field) {
|
|
176
|
+
if (value === null) return { exists: { field } };
|
|
177
|
+
if (typeof value === "object" && value !== null) {
|
|
178
|
+
if ("$eq" in value && value.$eq === null) return { exists: { field } };
|
|
179
|
+
if ("$ne" in value && value.$ne === null) return { bool: { must_not: [{ exists: { field } }] } };
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
translateOperator(operator, value, field) {
|
|
184
|
+
if (!this.isOperator(operator)) throw new Error(`Unsupported operator: ${operator}`);
|
|
185
|
+
if (operator === "$not" && field) {
|
|
186
|
+
const specialCaseResult = this.handleNotOperatorSpecialCases(value, field);
|
|
187
|
+
if (specialCaseResult) return specialCaseResult;
|
|
188
|
+
}
|
|
189
|
+
if (this.isLogicalOperator(operator)) {
|
|
190
|
+
if (operator === "$not" && field && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
191
|
+
const entries = Object.entries(value);
|
|
192
|
+
if (entries.length > 0) {
|
|
193
|
+
if (entries.every(([op]) => this.isOperator(op))) return { bool: { must_not: [this.translateFieldConditions(field, value)] } };
|
|
194
|
+
if (entries.length === 1 && entries[0] && this.isOperator(entries[0][0])) {
|
|
195
|
+
const [nestedOp, nestedVal] = entries[0];
|
|
196
|
+
return { bool: { must_not: [this.translateFieldOperator(field, nestedOp, nestedVal)] } };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return this.translateLogicalOperator(operator, value);
|
|
201
|
+
}
|
|
202
|
+
if (field) return this.translateFieldOperator(field, operator, value);
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Translates field conditions to ElasticSearch query syntax
|
|
207
|
+
* Handles special cases like range queries and multiple operators
|
|
208
|
+
*/
|
|
209
|
+
translateFieldConditions(field, conditions) {
|
|
210
|
+
if (this.canOptimizeToRangeQuery(conditions)) return this.createRangeQuery(field, conditions);
|
|
211
|
+
const queryConditions = [];
|
|
212
|
+
Object.entries(conditions).forEach(([operator, value]) => {
|
|
213
|
+
if (this.isOperator(operator)) queryConditions.push(this.translateOperator(operator, value, field));
|
|
214
|
+
else {
|
|
215
|
+
const fieldWithKeyword = this.addKeywordIfNeeded(`${field}.${operator}`, value);
|
|
216
|
+
queryConditions.push({ term: { [fieldWithKeyword]: value } });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
if (queryConditions.length === 1) return queryConditions[0];
|
|
220
|
+
return { bool: { must: queryConditions } };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Checks if conditions can be optimized to a range query
|
|
224
|
+
*/
|
|
225
|
+
canOptimizeToRangeQuery(conditions) {
|
|
226
|
+
return Object.keys(conditions).every((op) => this.isNumericOperator(op)) && Object.keys(conditions).length > 0;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Creates a range query from numeric operators
|
|
230
|
+
*/
|
|
231
|
+
createRangeQuery(field, conditions) {
|
|
232
|
+
const rangeParams = Object.fromEntries(Object.entries(conditions).map(([op, val]) => [op.replace("$", ""), this.normalizeComparisonValue(val)]));
|
|
233
|
+
return { range: { [field]: rangeParams } };
|
|
234
|
+
}
|
|
375
235
|
};
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/vector/index.ts
|
|
238
|
+
const METRIC_MAPPING = {
|
|
239
|
+
cosine: "cosine",
|
|
240
|
+
euclidean: "l2_norm",
|
|
241
|
+
dotproduct: "dot_product"
|
|
382
242
|
};
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
243
|
+
const REVERSE_METRIC_MAPPING = {
|
|
244
|
+
cosine: "cosine",
|
|
245
|
+
l2_norm: "euclidean",
|
|
246
|
+
dot_product: "dotproduct"
|
|
387
247
|
};
|
|
388
|
-
var ElasticSearchVector = class extends
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
throw new error.MastraError({
|
|
983
|
-
id: storage.createVectorErrorId("ELASTICSEARCH", "DELETE_VECTORS", "EMPTY_FILTER"),
|
|
984
|
-
domain: error.ErrorDomain.STORAGE,
|
|
985
|
-
category: error.ErrorCategory.USER,
|
|
986
|
-
text: "Cannot delete with empty filter",
|
|
987
|
-
details: { indexName }
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
try {
|
|
991
|
-
if (ids) {
|
|
992
|
-
const bulkBody = ids.flatMap((id) => [{ delete: { _index: indexName, _id: id } }]);
|
|
993
|
-
const response = await this.client.bulk({
|
|
994
|
-
operations: bulkBody,
|
|
995
|
-
refresh: true
|
|
996
|
-
});
|
|
997
|
-
if (response.errors) {
|
|
998
|
-
const failedItems = [];
|
|
999
|
-
const successfulIds = [];
|
|
1000
|
-
for (let i = 0; i < response.items.length; i++) {
|
|
1001
|
-
const item = response.items[i];
|
|
1002
|
-
if (!item) continue;
|
|
1003
|
-
const operationType = Object.keys(item)[0];
|
|
1004
|
-
const operationResult = item[operationType];
|
|
1005
|
-
if (!operationResult) continue;
|
|
1006
|
-
if (operationResult.error) {
|
|
1007
|
-
const operationIndex = i;
|
|
1008
|
-
const operationDoc = bulkBody[operationIndex];
|
|
1009
|
-
const failedId = operationDoc?.delete?._id || ids[i] || `unknown-${i}`;
|
|
1010
|
-
failedItems.push({
|
|
1011
|
-
id: failedId,
|
|
1012
|
-
status: operationResult.status || 0,
|
|
1013
|
-
error: operationResult.error
|
|
1014
|
-
});
|
|
1015
|
-
} else if (operationResult?.status && operationResult.status < 300) {
|
|
1016
|
-
const operationIndex = i;
|
|
1017
|
-
const operationDoc = bulkBody[operationIndex];
|
|
1018
|
-
const successId = operationDoc?.delete?._id || ids[i];
|
|
1019
|
-
if (successId) {
|
|
1020
|
-
successfulIds.push(successId);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
if (failedItems.length > 0) {
|
|
1025
|
-
const failedItemDetails = failedItems.map((item) => `${item.id}: ${item.error?.reason || item.error?.type || JSON.stringify(item.error)}`).join("; ");
|
|
1026
|
-
const mastraError = new error.MastraError(
|
|
1027
|
-
{
|
|
1028
|
-
id: storage.createVectorErrorId("ELASTICSEARCH", "DELETE_VECTORS", "BULK_PARTIAL_FAILURE"),
|
|
1029
|
-
text: `Bulk delete partially failed: ${failedItems.length} of ${response.items.length} operations failed. Failed items: ${failedItemDetails}`,
|
|
1030
|
-
domain: error.ErrorDomain.STORAGE,
|
|
1031
|
-
category: error.ErrorCategory.THIRD_PARTY,
|
|
1032
|
-
details: {
|
|
1033
|
-
indexName,
|
|
1034
|
-
totalOperations: response.items.length,
|
|
1035
|
-
failedCount: failedItems.length,
|
|
1036
|
-
successfulCount: successfulIds.length,
|
|
1037
|
-
failedItemIds: failedItems.map((item) => item.id).join(","),
|
|
1038
|
-
failedItemErrors: failedItemDetails
|
|
1039
|
-
}
|
|
1040
|
-
},
|
|
1041
|
-
new Error(`Bulk delete operation had ${failedItems.length} failures`)
|
|
1042
|
-
);
|
|
1043
|
-
this.logger?.error(mastraError.toString());
|
|
1044
|
-
this.logger?.trackException(mastraError);
|
|
1045
|
-
throw mastraError;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
} else if (filter) {
|
|
1049
|
-
const translator = new ElasticSearchFilterTranslator();
|
|
1050
|
-
const translatedFilter = translator.translate(filter);
|
|
1051
|
-
await this.client.deleteByQuery({
|
|
1052
|
-
index: indexName,
|
|
1053
|
-
query: translatedFilter || { match_all: {} },
|
|
1054
|
-
refresh: true
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
} catch (error$1) {
|
|
1058
|
-
if (error$1 instanceof error.MastraError) throw error$1;
|
|
1059
|
-
throw new error.MastraError(
|
|
1060
|
-
{
|
|
1061
|
-
id: storage.createVectorErrorId("ELASTICSEARCH", "DELETE_VECTORS", "FAILED"),
|
|
1062
|
-
domain: error.ErrorDomain.STORAGE,
|
|
1063
|
-
category: error.ErrorCategory.THIRD_PARTY,
|
|
1064
|
-
details: {
|
|
1065
|
-
indexName,
|
|
1066
|
-
...filter && { filter: JSON.stringify(filter) },
|
|
1067
|
-
...ids && { idsCount: ids.length }
|
|
1068
|
-
}
|
|
1069
|
-
},
|
|
1070
|
-
error$1
|
|
1071
|
-
);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
248
|
+
var ElasticSearchVector = class extends _mastra_core_vector.MastraVector {
|
|
249
|
+
client;
|
|
250
|
+
/**
|
|
251
|
+
* Creates a new ElasticSearchVector client.
|
|
252
|
+
*
|
|
253
|
+
* Accepts either a pre-configured ElasticSearch client or connection parameters:
|
|
254
|
+
* - `{ id, client }` - Use an existing ElasticSearch client
|
|
255
|
+
* - `{ id, url, auth? }` - Create a new client from connection parameters
|
|
256
|
+
*/
|
|
257
|
+
constructor(config) {
|
|
258
|
+
super({ id: config.id });
|
|
259
|
+
if ("client" in config && config.client) this.client = config.client;
|
|
260
|
+
else if ("url" in config && config.url) this.client = new _elastic_elasticsearch.Client({
|
|
261
|
+
node: config.url,
|
|
262
|
+
...config.auth && { auth: config.auth },
|
|
263
|
+
name: "mastra-elasticsearch",
|
|
264
|
+
headers: { "user-agent": `mastra-es/${version}` }
|
|
265
|
+
});
|
|
266
|
+
else throw new _mastra_core_error.MastraError({
|
|
267
|
+
id: "ELASTIC_SEARCH_CONSTRUCTOR_ERROR",
|
|
268
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
269
|
+
category: _mastra_core_error.ErrorCategory.SYSTEM,
|
|
270
|
+
text: "Invalid config: provide either { client } or { url }."
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Creates a new collection with the specified configuration.
|
|
275
|
+
*
|
|
276
|
+
* @param {string} indexName - The name of the collection to create.
|
|
277
|
+
* @param {number} dimension - The dimension of the vectors to be stored in the collection.
|
|
278
|
+
* @param {'cosine' | 'euclidean' | 'dotproduct'} [metric=cosine] - The metric to use to sort vectors in the collection.
|
|
279
|
+
* @returns {Promise<void>} A promise that resolves when the collection is created.
|
|
280
|
+
*/
|
|
281
|
+
async createIndex({ indexName, dimension, metric = "cosine" }) {
|
|
282
|
+
if (!Number.isInteger(dimension) || dimension <= 0) throw new _mastra_core_error.MastraError({
|
|
283
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "CREATE_INDEX", "INVALID_ARGS"),
|
|
284
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
285
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
286
|
+
text: "Dimension must be a positive integer",
|
|
287
|
+
details: {
|
|
288
|
+
indexName,
|
|
289
|
+
dimension
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
try {
|
|
293
|
+
await this.client.indices.create({
|
|
294
|
+
index: indexName,
|
|
295
|
+
mappings: { properties: {
|
|
296
|
+
metadata: { type: "object" },
|
|
297
|
+
embedding: {
|
|
298
|
+
type: "dense_vector",
|
|
299
|
+
dims: dimension,
|
|
300
|
+
index: true,
|
|
301
|
+
similarity: METRIC_MAPPING[metric]
|
|
302
|
+
}
|
|
303
|
+
} }
|
|
304
|
+
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
const message = error?.message || error?.toString();
|
|
307
|
+
if (message && message.toLowerCase().includes("already exists")) {
|
|
308
|
+
await this.validateExistingIndex(indexName, dimension, metric);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
throw new _mastra_core_error.MastraError({
|
|
312
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "CREATE_INDEX", "FAILED"),
|
|
313
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
314
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
315
|
+
details: {
|
|
316
|
+
indexName,
|
|
317
|
+
dimension,
|
|
318
|
+
metric
|
|
319
|
+
}
|
|
320
|
+
}, error);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Lists all indexes.
|
|
325
|
+
*
|
|
326
|
+
* @returns {Promise<string[]>} A promise that resolves to an array of indexes.
|
|
327
|
+
*/
|
|
328
|
+
async listIndexes() {
|
|
329
|
+
try {
|
|
330
|
+
return (await this.client.cat.indices({ format: "json" })).map((record) => record.index).filter((index) => index !== void 0 && !index.startsWith("."));
|
|
331
|
+
} catch (error) {
|
|
332
|
+
throw new _mastra_core_error.MastraError({
|
|
333
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "LIST_INDEXES", "FAILED"),
|
|
334
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
335
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY
|
|
336
|
+
}, error);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Validates that an existing index matches the requested dimension and metric.
|
|
341
|
+
* Throws an error if there's a mismatch, otherwise allows idempotent creation.
|
|
342
|
+
*/
|
|
343
|
+
async validateExistingIndex(indexName, dimension, metric) {
|
|
344
|
+
let info;
|
|
345
|
+
try {
|
|
346
|
+
info = await this.describeIndex({ indexName });
|
|
347
|
+
} catch (infoError) {
|
|
348
|
+
const mastraError = new _mastra_core_error.MastraError({
|
|
349
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "VALIDATE_INDEX", "FETCH_FAILED"),
|
|
350
|
+
text: `Index "${indexName}" already exists, but failed to fetch index info for dimension check.`,
|
|
351
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
352
|
+
category: _mastra_core_error.ErrorCategory.SYSTEM,
|
|
353
|
+
details: { indexName }
|
|
354
|
+
}, infoError);
|
|
355
|
+
this.logger?.trackException(mastraError);
|
|
356
|
+
this.logger?.error(mastraError.toString());
|
|
357
|
+
throw mastraError;
|
|
358
|
+
}
|
|
359
|
+
const existingDim = info?.dimension;
|
|
360
|
+
const existingMetric = info?.metric;
|
|
361
|
+
if (existingDim === dimension) {
|
|
362
|
+
this.logger?.info(`Index "${indexName}" already exists with ${existingDim} dimensions and metric ${existingMetric}, skipping creation.`);
|
|
363
|
+
if (existingMetric !== metric) this.logger?.warn(`Attempted to create index with metric "${metric}", but index already exists with metric "${existingMetric}". To use a different metric, delete and recreate the index.`);
|
|
364
|
+
} else if (info) {
|
|
365
|
+
const mastraError = new _mastra_core_error.MastraError({
|
|
366
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "VALIDATE_INDEX", "DIMENSION_MISMATCH"),
|
|
367
|
+
text: `Index "${indexName}" already exists with ${existingDim} dimensions, but ${dimension} dimensions were requested`,
|
|
368
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
369
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
370
|
+
details: {
|
|
371
|
+
indexName,
|
|
372
|
+
existingDim,
|
|
373
|
+
requestedDim: dimension
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
this.logger?.trackException(mastraError);
|
|
377
|
+
this.logger?.error(mastraError.toString());
|
|
378
|
+
throw mastraError;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Retrieves statistics about a vector index.
|
|
383
|
+
*
|
|
384
|
+
* @param {string} indexName - The name of the index to describe
|
|
385
|
+
* @returns A promise that resolves to the index statistics including dimension, count and metric
|
|
386
|
+
*/
|
|
387
|
+
async describeIndex({ indexName }) {
|
|
388
|
+
const embedding = ((await this.client.indices.get({ index: indexName }))[indexName]?.mappings)?.properties?.embedding;
|
|
389
|
+
const similarity = embedding.similarity;
|
|
390
|
+
const countInfo = await this.client.count({ index: indexName });
|
|
391
|
+
return {
|
|
392
|
+
dimension: Number(embedding.dims),
|
|
393
|
+
count: Number(countInfo.count),
|
|
394
|
+
metric: REVERSE_METRIC_MAPPING[similarity]
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Deletes the specified index.
|
|
399
|
+
*
|
|
400
|
+
* @param {string} indexName - The name of the index to delete.
|
|
401
|
+
* @returns {Promise<void>} A promise that resolves when the index is deleted.
|
|
402
|
+
*/
|
|
403
|
+
async deleteIndex({ indexName }) {
|
|
404
|
+
try {
|
|
405
|
+
await this.client.indices.delete({ index: indexName }, { ignore: [404] });
|
|
406
|
+
} catch (error) {
|
|
407
|
+
const mastraError = new _mastra_core_error.MastraError({
|
|
408
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_INDEX", "FAILED"),
|
|
409
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
410
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
411
|
+
details: { indexName }
|
|
412
|
+
}, error);
|
|
413
|
+
this.logger?.error(mastraError.toString());
|
|
414
|
+
this.logger?.trackException(mastraError);
|
|
415
|
+
throw mastraError;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Inserts or updates vectors in the specified collection.
|
|
420
|
+
*
|
|
421
|
+
* @param {string} indexName - The name of the collection to upsert into.
|
|
422
|
+
* @param {number[][]} vectors - An array of vectors to upsert.
|
|
423
|
+
* @param {Record<string, any>[]} [metadata] - An optional array of metadata objects corresponding to each vector.
|
|
424
|
+
* @param {string[]} [ids] - An optional array of IDs corresponding to each vector. If not provided, new IDs will be generated.
|
|
425
|
+
* @returns {Promise<string[]>} A promise that resolves to an array of IDs of the upserted vectors.
|
|
426
|
+
*/
|
|
427
|
+
async upsert({ indexName, vectors, metadata = [], ids }) {
|
|
428
|
+
(0, _mastra_core_vector.validateUpsert)("ELASTICSEARCH", vectors, metadata, ids, true);
|
|
429
|
+
const vectorIds = ids || vectors.map(() => crypto.randomUUID());
|
|
430
|
+
const operations = [];
|
|
431
|
+
try {
|
|
432
|
+
const indexInfo = await this.describeIndex({ indexName });
|
|
433
|
+
this.validateVectorDimensions(vectors, indexInfo.dimension);
|
|
434
|
+
for (let i = 0; i < vectors.length; i++) {
|
|
435
|
+
const operation = { index: {
|
|
436
|
+
_index: indexName,
|
|
437
|
+
_id: vectorIds[i]
|
|
438
|
+
} };
|
|
439
|
+
const document = {
|
|
440
|
+
embedding: vectors[i],
|
|
441
|
+
metadata: metadata[i] || {}
|
|
442
|
+
};
|
|
443
|
+
operations.push(operation);
|
|
444
|
+
operations.push(document);
|
|
445
|
+
}
|
|
446
|
+
if (operations.length > 0) {
|
|
447
|
+
const response = await this.client.bulk({
|
|
448
|
+
operations,
|
|
449
|
+
refresh: true
|
|
450
|
+
});
|
|
451
|
+
if (response.errors) {
|
|
452
|
+
const failedItems = [];
|
|
453
|
+
const successfulIds = [];
|
|
454
|
+
for (let i = 0; i < response.items.length; i++) {
|
|
455
|
+
const item = response.items[i];
|
|
456
|
+
if (!item) continue;
|
|
457
|
+
const operationResult = item[Object.keys(item)[0]];
|
|
458
|
+
if (!operationResult) continue;
|
|
459
|
+
if (operationResult.error) {
|
|
460
|
+
const failedId = operations[i * 2]?.index?._id || vectorIds[i] || `unknown-${i}`;
|
|
461
|
+
failedItems.push({
|
|
462
|
+
id: failedId,
|
|
463
|
+
status: operationResult.status || 0,
|
|
464
|
+
error: operationResult.error
|
|
465
|
+
});
|
|
466
|
+
} else if (operationResult?.status && operationResult.status < 300) {
|
|
467
|
+
const successId = operations[i * 2]?.index?._id || vectorIds[i];
|
|
468
|
+
if (successId) successfulIds.push(successId);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (failedItems.length > 0) {
|
|
472
|
+
const failedItemDetails = failedItems.map((item) => `${item.id}: ${item.error?.reason || item.error?.type || JSON.stringify(item.error)}`).join("; ");
|
|
473
|
+
const mastraError = new _mastra_core_error.MastraError({
|
|
474
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPSERT", "BULK_PARTIAL_FAILURE"),
|
|
475
|
+
text: `Bulk upsert partially failed: ${failedItems.length} of ${response.items.length} operations failed. Failed items: ${failedItemDetails}`,
|
|
476
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
477
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
478
|
+
details: {
|
|
479
|
+
indexName,
|
|
480
|
+
totalOperations: response.items.length,
|
|
481
|
+
failedCount: failedItems.length,
|
|
482
|
+
successfulCount: successfulIds.length,
|
|
483
|
+
failedItemIds: failedItems.map((item) => item.id).join(","),
|
|
484
|
+
failedItemErrors: failedItemDetails
|
|
485
|
+
}
|
|
486
|
+
}, /* @__PURE__ */ new Error(`Bulk operation had ${failedItems.length} failures`));
|
|
487
|
+
this.logger?.error(mastraError.toString());
|
|
488
|
+
this.logger?.trackException(mastraError);
|
|
489
|
+
throw mastraError;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return vectorIds;
|
|
494
|
+
} catch (error) {
|
|
495
|
+
throw new _mastra_core_error.MastraError({
|
|
496
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPSERT", "FAILED"),
|
|
497
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
498
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
499
|
+
details: {
|
|
500
|
+
indexName,
|
|
501
|
+
vectorCount: vectors?.length || 0
|
|
502
|
+
}
|
|
503
|
+
}, error);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Queries the specified collection using a vector and optional filter.
|
|
508
|
+
*
|
|
509
|
+
* @param {string} indexName - The name of the collection to query.
|
|
510
|
+
* @param {number[]} queryVector - The vector to query with.
|
|
511
|
+
* @param {number} [topK] - The maximum number of results to return.
|
|
512
|
+
* @param {Record<string, any>} [filter] - An optional filter to apply to the query.
|
|
513
|
+
* @param {boolean} [includeVectors=false] - Whether to include the vectors in the response.
|
|
514
|
+
* @returns {Promise<QueryResult[]>} A promise that resolves to an array of query results.
|
|
515
|
+
*/
|
|
516
|
+
async query({ indexName, queryVector, filter, topK = 10, includeVector = false }) {
|
|
517
|
+
if (!queryVector) throw new _mastra_core_error.MastraError({
|
|
518
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "QUERY", "MISSING_VECTOR"),
|
|
519
|
+
text: "queryVector is required for Elasticsearch queries. Metadata-only queries are not supported by this vector store.",
|
|
520
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
521
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
522
|
+
details: { indexName }
|
|
523
|
+
});
|
|
524
|
+
(0, _mastra_core_vector.validateTopK)("ELASTICSEARCH", topK);
|
|
525
|
+
try {
|
|
526
|
+
const translatedFilter = this.transformFilter(filter);
|
|
527
|
+
const sourceFields = includeVector ? ["metadata", "embedding"] : ["metadata"];
|
|
528
|
+
return (await this.client.search({
|
|
529
|
+
index: indexName,
|
|
530
|
+
knn: {
|
|
531
|
+
field: "embedding",
|
|
532
|
+
query_vector: queryVector,
|
|
533
|
+
k: topK,
|
|
534
|
+
num_candidates: topK * 2,
|
|
535
|
+
...translatedFilter ? { filter: translatedFilter } : {}
|
|
536
|
+
},
|
|
537
|
+
_source: sourceFields
|
|
538
|
+
})).hits.hits.map((hit) => {
|
|
539
|
+
const source = hit._source || {};
|
|
540
|
+
return {
|
|
541
|
+
id: String(hit._id),
|
|
542
|
+
score: typeof hit._score === "number" ? hit._score : 0,
|
|
543
|
+
metadata: source.metadata || {},
|
|
544
|
+
...includeVector && { vector: source.embedding }
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
} catch (error) {
|
|
548
|
+
throw new _mastra_core_error.MastraError({
|
|
549
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "QUERY", "FAILED"),
|
|
550
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
551
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
552
|
+
details: {
|
|
553
|
+
indexName,
|
|
554
|
+
topK
|
|
555
|
+
}
|
|
556
|
+
}, error);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Validates the dimensions of the vectors.
|
|
561
|
+
*
|
|
562
|
+
* @param {number[][]} vectors - The vectors to validate.
|
|
563
|
+
* @param {number} dimension - The dimension of the vectors.
|
|
564
|
+
* @returns {void}
|
|
565
|
+
*/
|
|
566
|
+
validateVectorDimensions(vectors, dimension) {
|
|
567
|
+
if (vectors.some((vector) => vector.length !== dimension)) throw new Error("Vector dimension does not match index dimension");
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Transforms the filter to the ElasticSearch DSL.
|
|
571
|
+
*
|
|
572
|
+
* @param {ElasticSearchVectorFilter} filter - The filter to transform.
|
|
573
|
+
* @returns {Record<string, any>} The transformed filter.
|
|
574
|
+
*/
|
|
575
|
+
transformFilter(filter) {
|
|
576
|
+
return new ElasticSearchFilterTranslator().translate(filter);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Updates vectors by ID or filter with the provided vector and/or metadata.
|
|
580
|
+
* @param params - Parameters containing either id or filter for targeting vectors to update
|
|
581
|
+
* @param params.indexName - The name of the index containing the vector(s).
|
|
582
|
+
* @param params.id - The ID of a single vector to update (mutually exclusive with filter).
|
|
583
|
+
* @param params.filter - A filter to match multiple vectors to update (mutually exclusive with id).
|
|
584
|
+
* @param params.update - An object containing the vector and/or metadata to update.
|
|
585
|
+
* @returns A promise that resolves when the update is complete.
|
|
586
|
+
* @throws Will throw an error if no updates are provided or if the update operation fails.
|
|
587
|
+
*/
|
|
588
|
+
async updateVector(params) {
|
|
589
|
+
const { indexName, update } = params;
|
|
590
|
+
if ("id" in params && "filter" in params && params.id && params.filter) throw new _mastra_core_error.MastraError({
|
|
591
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR", "MUTUALLY_EXCLUSIVE"),
|
|
592
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
593
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
594
|
+
text: "id and filter are mutually exclusive",
|
|
595
|
+
details: { indexName }
|
|
596
|
+
});
|
|
597
|
+
if (!update.vector && !update.metadata) throw new _mastra_core_error.MastraError({
|
|
598
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR", "NO_UPDATES"),
|
|
599
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
600
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
601
|
+
text: "No updates provided",
|
|
602
|
+
details: { indexName }
|
|
603
|
+
});
|
|
604
|
+
if ("filter" in params && params.filter && Object.keys(params.filter).length === 0) throw new _mastra_core_error.MastraError({
|
|
605
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR", "EMPTY_FILTER"),
|
|
606
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
607
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
608
|
+
text: "Cannot update with empty filter",
|
|
609
|
+
details: { indexName }
|
|
610
|
+
});
|
|
611
|
+
if ("id" in params && params.id) await this.updateVectorById(indexName, params.id, update);
|
|
612
|
+
else if ("filter" in params && params.filter) await this.updateVectorsByFilter(indexName, params.filter, update);
|
|
613
|
+
else throw new _mastra_core_error.MastraError({
|
|
614
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR", "NO_TARGET"),
|
|
615
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
616
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
617
|
+
text: "Either id or filter must be provided",
|
|
618
|
+
details: { indexName }
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Updates a single vector by its ID.
|
|
623
|
+
*/
|
|
624
|
+
async updateVectorById(indexName, id, update) {
|
|
625
|
+
let existingDoc;
|
|
626
|
+
try {
|
|
627
|
+
const result = await this.client.get({
|
|
628
|
+
index: indexName,
|
|
629
|
+
id,
|
|
630
|
+
_source: ["embedding", "metadata"]
|
|
631
|
+
}).catch(() => {
|
|
632
|
+
throw new Error(`Document with ID ${id} not found in index ${indexName}`);
|
|
633
|
+
});
|
|
634
|
+
if (!result || !result._source) throw new Error(`Document with ID ${id} has no source data in index ${indexName}`);
|
|
635
|
+
existingDoc = result;
|
|
636
|
+
} catch (error) {
|
|
637
|
+
throw new _mastra_core_error.MastraError({
|
|
638
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR", "FAILED"),
|
|
639
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
640
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
641
|
+
details: {
|
|
642
|
+
indexName,
|
|
643
|
+
id
|
|
644
|
+
}
|
|
645
|
+
}, error);
|
|
646
|
+
}
|
|
647
|
+
const source = existingDoc._source;
|
|
648
|
+
const updatedDoc = {};
|
|
649
|
+
try {
|
|
650
|
+
if (update.vector) {
|
|
651
|
+
const indexInfo = await this.describeIndex({ indexName });
|
|
652
|
+
this.validateVectorDimensions([update.vector], indexInfo.dimension);
|
|
653
|
+
updatedDoc.embedding = update.vector;
|
|
654
|
+
} else if (source?.embedding) updatedDoc.embedding = source.embedding;
|
|
655
|
+
if (update.metadata) updatedDoc.metadata = update.metadata;
|
|
656
|
+
else updatedDoc.metadata = source?.metadata || {};
|
|
657
|
+
await this.client.index({
|
|
658
|
+
index: indexName,
|
|
659
|
+
id,
|
|
660
|
+
document: updatedDoc,
|
|
661
|
+
refresh: true
|
|
662
|
+
});
|
|
663
|
+
} catch (error) {
|
|
664
|
+
throw new _mastra_core_error.MastraError({
|
|
665
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR", "FAILED"),
|
|
666
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
667
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
668
|
+
details: {
|
|
669
|
+
indexName,
|
|
670
|
+
id
|
|
671
|
+
}
|
|
672
|
+
}, error);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Updates multiple vectors matching a filter.
|
|
677
|
+
*/
|
|
678
|
+
async updateVectorsByFilter(indexName, filter, update) {
|
|
679
|
+
try {
|
|
680
|
+
const translatedFilter = new ElasticSearchFilterTranslator().translate(filter);
|
|
681
|
+
const scriptSource = [];
|
|
682
|
+
const scriptParams = {};
|
|
683
|
+
if (update.vector) {
|
|
684
|
+
scriptSource.push("ctx._source.embedding = params.embedding");
|
|
685
|
+
scriptParams.embedding = update.vector;
|
|
686
|
+
}
|
|
687
|
+
if (update.metadata) {
|
|
688
|
+
scriptSource.push("ctx._source.metadata = params.metadata");
|
|
689
|
+
scriptParams.metadata = update.metadata;
|
|
690
|
+
}
|
|
691
|
+
await this.client.updateByQuery({
|
|
692
|
+
index: indexName,
|
|
693
|
+
query: translatedFilter || { match_all: {} },
|
|
694
|
+
script: {
|
|
695
|
+
source: scriptSource.join("; "),
|
|
696
|
+
params: scriptParams,
|
|
697
|
+
lang: "painless"
|
|
698
|
+
},
|
|
699
|
+
refresh: true
|
|
700
|
+
});
|
|
701
|
+
} catch (error) {
|
|
702
|
+
throw new _mastra_core_error.MastraError({
|
|
703
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "UPDATE_VECTOR_BY_FILTER", "FAILED"),
|
|
704
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
705
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
706
|
+
details: {
|
|
707
|
+
indexName,
|
|
708
|
+
filter: JSON.stringify(filter)
|
|
709
|
+
}
|
|
710
|
+
}, error);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Deletes a vector by its ID.
|
|
715
|
+
* @param indexName - The name of the index containing the vector.
|
|
716
|
+
* @param id - The ID of the vector to delete.
|
|
717
|
+
* @returns A promise that resolves when the deletion is complete.
|
|
718
|
+
* @throws Will throw an error if the deletion operation fails.
|
|
719
|
+
*/
|
|
720
|
+
async deleteVector({ indexName, id }) {
|
|
721
|
+
try {
|
|
722
|
+
await this.client.delete({
|
|
723
|
+
index: indexName,
|
|
724
|
+
id,
|
|
725
|
+
refresh: true
|
|
726
|
+
});
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (error && typeof error === "object" && "statusCode" in error && error.statusCode === 404) return;
|
|
729
|
+
throw new _mastra_core_error.MastraError({
|
|
730
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTOR", "FAILED"),
|
|
731
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
732
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
733
|
+
details: {
|
|
734
|
+
indexName,
|
|
735
|
+
...id && { id }
|
|
736
|
+
}
|
|
737
|
+
}, error);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async deleteVectors({ indexName, filter, ids }) {
|
|
741
|
+
if (ids && filter) throw new _mastra_core_error.MastraError({
|
|
742
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTORS", "MUTUALLY_EXCLUSIVE"),
|
|
743
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
744
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
745
|
+
text: "ids and filter are mutually exclusive",
|
|
746
|
+
details: { indexName }
|
|
747
|
+
});
|
|
748
|
+
if (!ids && !filter) throw new _mastra_core_error.MastraError({
|
|
749
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTORS", "NO_TARGET"),
|
|
750
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
751
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
752
|
+
text: "Either filter or ids must be provided",
|
|
753
|
+
details: { indexName }
|
|
754
|
+
});
|
|
755
|
+
if (ids && ids.length === 0) throw new _mastra_core_error.MastraError({
|
|
756
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTORS", "EMPTY_IDS"),
|
|
757
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
758
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
759
|
+
text: "Cannot delete with empty ids array",
|
|
760
|
+
details: { indexName }
|
|
761
|
+
});
|
|
762
|
+
if (filter && Object.keys(filter).length === 0) throw new _mastra_core_error.MastraError({
|
|
763
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTORS", "EMPTY_FILTER"),
|
|
764
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
765
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
766
|
+
text: "Cannot delete with empty filter",
|
|
767
|
+
details: { indexName }
|
|
768
|
+
});
|
|
769
|
+
try {
|
|
770
|
+
if (ids) {
|
|
771
|
+
const bulkBody = ids.flatMap((id) => [{ delete: {
|
|
772
|
+
_index: indexName,
|
|
773
|
+
_id: id
|
|
774
|
+
} }]);
|
|
775
|
+
const response = await this.client.bulk({
|
|
776
|
+
operations: bulkBody,
|
|
777
|
+
refresh: true
|
|
778
|
+
});
|
|
779
|
+
if (response.errors) {
|
|
780
|
+
const failedItems = [];
|
|
781
|
+
const successfulIds = [];
|
|
782
|
+
for (let i = 0; i < response.items.length; i++) {
|
|
783
|
+
const item = response.items[i];
|
|
784
|
+
if (!item) continue;
|
|
785
|
+
const operationResult = item[Object.keys(item)[0]];
|
|
786
|
+
if (!operationResult) continue;
|
|
787
|
+
if (operationResult.error) {
|
|
788
|
+
const failedId = bulkBody[i]?.delete?._id || ids[i] || `unknown-${i}`;
|
|
789
|
+
failedItems.push({
|
|
790
|
+
id: failedId,
|
|
791
|
+
status: operationResult.status || 0,
|
|
792
|
+
error: operationResult.error
|
|
793
|
+
});
|
|
794
|
+
} else if (operationResult?.status && operationResult.status < 300) {
|
|
795
|
+
const successId = bulkBody[i]?.delete?._id || ids[i];
|
|
796
|
+
if (successId) successfulIds.push(successId);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (failedItems.length > 0) {
|
|
800
|
+
const failedItemDetails = failedItems.map((item) => `${item.id}: ${item.error?.reason || item.error?.type || JSON.stringify(item.error)}`).join("; ");
|
|
801
|
+
const mastraError = new _mastra_core_error.MastraError({
|
|
802
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTORS", "BULK_PARTIAL_FAILURE"),
|
|
803
|
+
text: `Bulk delete partially failed: ${failedItems.length} of ${response.items.length} operations failed. Failed items: ${failedItemDetails}`,
|
|
804
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
805
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
806
|
+
details: {
|
|
807
|
+
indexName,
|
|
808
|
+
totalOperations: response.items.length,
|
|
809
|
+
failedCount: failedItems.length,
|
|
810
|
+
successfulCount: successfulIds.length,
|
|
811
|
+
failedItemIds: failedItems.map((item) => item.id).join(","),
|
|
812
|
+
failedItemErrors: failedItemDetails
|
|
813
|
+
}
|
|
814
|
+
}, /* @__PURE__ */ new Error(`Bulk delete operation had ${failedItems.length} failures`));
|
|
815
|
+
this.logger?.error(mastraError.toString());
|
|
816
|
+
this.logger?.trackException(mastraError);
|
|
817
|
+
throw mastraError;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
} else if (filter) {
|
|
821
|
+
const translatedFilter = new ElasticSearchFilterTranslator().translate(filter);
|
|
822
|
+
await this.client.deleteByQuery({
|
|
823
|
+
index: indexName,
|
|
824
|
+
query: translatedFilter || { match_all: {} },
|
|
825
|
+
refresh: true
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (error instanceof _mastra_core_error.MastraError) throw error;
|
|
830
|
+
throw new _mastra_core_error.MastraError({
|
|
831
|
+
id: (0, _mastra_core_storage.createVectorErrorId)("ELASTICSEARCH", "DELETE_VECTORS", "FAILED"),
|
|
832
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
833
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
834
|
+
details: {
|
|
835
|
+
indexName,
|
|
836
|
+
...filter && { filter: JSON.stringify(filter) },
|
|
837
|
+
...ids && { idsCount: ids.length }
|
|
838
|
+
}
|
|
839
|
+
}, error);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
1074
842
|
};
|
|
1075
|
-
|
|
843
|
+
//#endregion
|
|
1076
844
|
exports.ElasticSearchVector = ElasticSearchVector;
|
|
1077
|
-
|
|
845
|
+
|
|
1078
846
|
//# sourceMappingURL=index.cjs.map
|