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