@mastra/opensearch 0.0.0-pass-headers-for-create-mastra-client-20250530010057 → 0.0.0-remove-unused-import-20250909212718

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.
@@ -1,461 +0,0 @@
1
- import type { FieldCondition, OperatorSupport, QueryOperator, VectorFilter } from '@mastra/core/vector/filter';
2
- import { BaseFilterTranslator } from '@mastra/core/vector/filter';
3
-
4
- /**
5
- * Translator for OpenSearch filter queries.
6
- * Maintains OpenSearch-compatible syntax while ensuring proper validation
7
- * and normalization of values.
8
- */
9
- export class OpenSearchFilterTranslator extends BaseFilterTranslator {
10
- protected override getSupportedOperators(): OperatorSupport {
11
- return {
12
- ...BaseFilterTranslator.DEFAULT_OPERATORS,
13
- logical: ['$and', '$or', '$not'],
14
- array: ['$in', '$nin', '$all'],
15
- element: ['$exists'],
16
- regex: ['$regex'],
17
- custom: [],
18
- };
19
- }
20
-
21
- translate(filter?: VectorFilter): VectorFilter {
22
- if (this.isEmpty(filter)) return undefined;
23
- this.validateFilter(filter);
24
- return this.translateNode(filter);
25
- }
26
-
27
- private translateNode(node: VectorFilter | FieldCondition): any {
28
- // Handle primitive values and arrays
29
- if (this.isPrimitive(node) || Array.isArray(node)) {
30
- return node;
31
- }
32
-
33
- const entries = Object.entries(node as Record<string, any>);
34
-
35
- // Extract logical operators and field conditions
36
- const logicalOperators: [string, any][] = [];
37
- const fieldConditions: [string, any][] = [];
38
-
39
- entries.forEach(([key, value]) => {
40
- if (this.isLogicalOperator(key)) {
41
- logicalOperators.push([key, value]);
42
- } else {
43
- fieldConditions.push([key, value]);
44
- }
45
- });
46
-
47
- // If we have a single logical operator
48
- if (logicalOperators.length === 1 && fieldConditions.length === 0) {
49
- const [operator, value] = logicalOperators[0] as [QueryOperator, any];
50
- if (!Array.isArray(value) && typeof value !== 'object') {
51
- throw new Error(`Invalid logical operator structure: ${operator} must have an array or object value`);
52
- }
53
- return this.translateLogicalOperator(operator, value);
54
- }
55
-
56
- // Process field conditions
57
- const fieldConditionQueries = fieldConditions.map(([key, value]) => {
58
- // Handle nested objects
59
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
60
- // Check if the object contains operators
61
- const hasOperators = Object.keys(value).some(k => this.isOperator(k));
62
-
63
- // Use a more direct approach based on whether operators are present
64
- const nestedField = `metadata.${key}`;
65
- return hasOperators
66
- ? this.translateFieldConditions(nestedField, value)
67
- : this.translateNestedObject(nestedField, value);
68
- }
69
-
70
- // Handle arrays
71
- if (Array.isArray(value)) {
72
- const fieldWithKeyword = this.addKeywordIfNeeded(`metadata.${key}`, value);
73
- return { terms: { [fieldWithKeyword]: value } };
74
- }
75
-
76
- // Handle simple field equality
77
- const fieldWithKeyword = this.addKeywordIfNeeded(`metadata.${key}`, value);
78
- return { term: { [fieldWithKeyword]: value } };
79
- });
80
-
81
- // Handle case with both logical operators and field conditions or multiple logical operators
82
- if (logicalOperators.length > 0) {
83
- const logicalConditions = logicalOperators.map(([operator, value]) =>
84
- this.translateOperator(operator as QueryOperator, value),
85
- );
86
-
87
- return {
88
- bool: {
89
- must: [...logicalConditions, ...fieldConditionQueries],
90
- },
91
- };
92
- }
93
-
94
- // If we only have field conditions
95
- if (fieldConditionQueries.length > 1) {
96
- return {
97
- bool: {
98
- must: fieldConditionQueries,
99
- },
100
- };
101
- }
102
-
103
- // If we have only one field condition
104
- if (fieldConditionQueries.length === 1) {
105
- return fieldConditionQueries[0];
106
- }
107
-
108
- // If we have no conditions (e.g., only empty $and arrays)
109
- return { match_all: {} };
110
- }
111
-
112
- /**
113
- * Handles translation of nested objects with dot notation fields
114
- */
115
- private translateNestedObject(field: string, value: Record<string, any>): any {
116
- const conditions = Object.entries(value).map(([subField, subValue]) => {
117
- const fullField = `${field}.${subField}`;
118
-
119
- // Check if this is an operator in a nested field
120
- if (this.isOperator(subField)) {
121
- return this.translateOperator(subField as QueryOperator, subValue, field);
122
- }
123
-
124
- if (typeof subValue === 'object' && subValue !== null && !Array.isArray(subValue)) {
125
- // Check if the nested object contains operators
126
- const hasOperators = Object.keys(subValue).some(k => this.isOperator(k));
127
- if (hasOperators) {
128
- return this.translateFieldConditions(fullField, subValue);
129
- }
130
- return this.translateNestedObject(fullField, subValue);
131
- }
132
- const fieldWithKeyword = this.addKeywordIfNeeded(fullField, subValue);
133
- return { term: { [fieldWithKeyword]: subValue } };
134
- });
135
-
136
- return {
137
- bool: {
138
- must: conditions,
139
- },
140
- };
141
- }
142
-
143
- private translateLogicalOperator(operator: QueryOperator, value: any): any {
144
- const conditions = Array.isArray(value) ? value.map(item => this.translateNode(item)) : [this.translateNode(value)];
145
- switch (operator) {
146
- case '$and':
147
- // For empty $and, return a query that matches everything
148
- if (Array.isArray(value) && value.length === 0) {
149
- return { match_all: {} };
150
- }
151
- return {
152
- bool: {
153
- must: conditions,
154
- },
155
- };
156
- case '$or':
157
- // For empty $or, return a query that matches nothing
158
- if (Array.isArray(value) && value.length === 0) {
159
- return {
160
- bool: {
161
- must_not: [{ match_all: {} }],
162
- },
163
- };
164
- }
165
- return {
166
- bool: {
167
- should: conditions,
168
- },
169
- };
170
- case '$not':
171
- return {
172
- bool: {
173
- must_not: conditions,
174
- },
175
- };
176
- default:
177
- return value;
178
- }
179
- }
180
-
181
- private translateFieldOperator(field: string, operator: QueryOperator, value: any): any {
182
- // Handle basic comparison operators
183
- if (this.isBasicOperator(operator)) {
184
- const normalizedValue = this.normalizeComparisonValue(value);
185
- const fieldWithKeyword = this.addKeywordIfNeeded(field, value);
186
- switch (operator) {
187
- case '$eq':
188
- return { term: { [fieldWithKeyword]: normalizedValue } };
189
- case '$ne':
190
- return {
191
- bool: {
192
- must_not: [{ term: { [fieldWithKeyword]: normalizedValue } }],
193
- },
194
- };
195
- default:
196
- return { term: { [fieldWithKeyword]: normalizedValue } };
197
- }
198
- }
199
-
200
- // Handle numeric operators
201
- if (this.isNumericOperator(operator)) {
202
- const normalizedValue = this.normalizeComparisonValue(value);
203
- const rangeOp = operator.replace('$', '');
204
- return { range: { [field]: { [rangeOp]: normalizedValue } } };
205
- }
206
-
207
- // Handle array operators
208
- if (this.isArrayOperator(operator)) {
209
- if (!Array.isArray(value)) {
210
- throw new Error(`Invalid array operator value: ${operator} requires an array value`);
211
- }
212
- const normalizedValues = this.normalizeArrayValues(value);
213
- const fieldWithKeyword = this.addKeywordIfNeeded(field, value);
214
- switch (operator) {
215
- case '$in':
216
- return { terms: { [fieldWithKeyword]: normalizedValues } };
217
- case '$nin':
218
- // For empty arrays, return a query that matches everything
219
- if (normalizedValues.length === 0) {
220
- return { match_all: {} };
221
- }
222
- return {
223
- bool: {
224
- must_not: [{ terms: { [fieldWithKeyword]: normalizedValues } }],
225
- },
226
- };
227
- case '$all':
228
- // For empty arrays, return a query that will match nothing
229
- if (normalizedValues.length === 0) {
230
- return {
231
- bool: {
232
- must_not: [{ match_all: {} }],
233
- },
234
- };
235
- }
236
- return {
237
- bool: {
238
- must: normalizedValues.map(v => ({ term: { [fieldWithKeyword]: v } })),
239
- },
240
- };
241
- default:
242
- return { terms: { [fieldWithKeyword]: normalizedValues } };
243
- }
244
- }
245
-
246
- // Handle element operators
247
- if (this.isElementOperator(operator)) {
248
- switch (operator) {
249
- case '$exists':
250
- return value ? { exists: { field } } : { bool: { must_not: [{ exists: { field } }] } };
251
- default:
252
- return { exists: { field } };
253
- }
254
- }
255
-
256
- // Handle regex operators
257
- if (this.isRegexOperator(operator)) {
258
- return this.translateRegexOperator(field, value);
259
- }
260
-
261
- const fieldWithKeyword = this.addKeywordIfNeeded(field, value);
262
- return { term: { [fieldWithKeyword]: value } };
263
- }
264
-
265
- /**
266
- * Translates regex patterns to OpenSearch query syntax
267
- */
268
- private translateRegexOperator(field: string, value: any): any {
269
- // Convert value to string if it's not already
270
- const regexValue = typeof value === 'string' ? value : value.toString();
271
-
272
- // Check for problematic patterns (like newlines, etc.)
273
- if (regexValue.includes('\n') || regexValue.includes('\r')) {
274
- // For patterns with newlines, use a simpler approach
275
- // OpenSearch doesn't support dotall flag like JavaScript
276
- return { match: { [field]: value } };
277
- }
278
-
279
- // Process regex pattern to handle anchors properly
280
- let processedRegex = regexValue;
281
- const hasStartAnchor = regexValue.startsWith('^');
282
- const hasEndAnchor = regexValue.endsWith('$');
283
-
284
- // If we have anchors, use wildcard query for better handling
285
- if (hasStartAnchor || hasEndAnchor) {
286
- // Remove anchors
287
- if (hasStartAnchor) {
288
- processedRegex = processedRegex.substring(1);
289
- }
290
- if (hasEndAnchor) {
291
- processedRegex = processedRegex.substring(0, processedRegex.length - 1);
292
- }
293
-
294
- // Create wildcard pattern
295
- let wildcardPattern = processedRegex;
296
- if (!hasStartAnchor) {
297
- wildcardPattern = '*' + wildcardPattern;
298
- }
299
- if (!hasEndAnchor) {
300
- wildcardPattern = wildcardPattern + '*';
301
- }
302
-
303
- return { wildcard: { [field]: wildcardPattern } };
304
- }
305
-
306
- // Use regexp for other regex patterns
307
- // Escape any backslashes to prevent OpenSearch from misinterpreting them
308
- const escapedRegex = regexValue.replace(/\\/g, '\\\\');
309
- return { regexp: { [field]: escapedRegex } };
310
- }
311
-
312
- private addKeywordIfNeeded(field: string, value: any): string {
313
- // Add .keyword suffix for string fields
314
- if (typeof value === 'string') {
315
- return `${field}.keyword`;
316
- }
317
- // Add .keyword suffix for string array fields
318
- if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
319
- return `${field}.keyword`;
320
- }
321
- return field;
322
- }
323
-
324
- /**
325
- * Helper method to handle special cases for the $not operator
326
- */
327
- private handleNotOperatorSpecialCases(value: any, field: string): any | null {
328
- // For "not null", we need to use exists query
329
- if (value === null) {
330
- return { exists: { field } };
331
- }
332
-
333
- if (typeof value === 'object' && value !== null) {
334
- // For "not {$eq: null}", we need to use exists query
335
- if ('$eq' in value && value.$eq === null) {
336
- return { exists: { field } };
337
- }
338
-
339
- // For "not {$ne: null}", we need to use must_not exists query
340
- if ('$ne' in value && value.$ne === null) {
341
- return {
342
- bool: {
343
- must_not: [{ exists: { field } }],
344
- },
345
- };
346
- }
347
- }
348
-
349
- return null; // No special case applies
350
- }
351
-
352
- private translateOperator(operator: QueryOperator, value: any, field?: string): any {
353
- // Check if this is a valid operator
354
- if (!this.isOperator(operator)) {
355
- throw new Error(`Unsupported operator: ${operator}`);
356
- }
357
-
358
- // Special case for $not with null or $eq: null
359
- if (operator === '$not' && field) {
360
- const specialCaseResult = this.handleNotOperatorSpecialCases(value, field);
361
- if (specialCaseResult) {
362
- return specialCaseResult;
363
- }
364
- }
365
-
366
- // Handle logical operators
367
- if (this.isLogicalOperator(operator)) {
368
- // For $not operator with field context and nested operators, handle specially
369
- if (operator === '$not' && field && typeof value === 'object' && value !== null && !Array.isArray(value)) {
370
- const entries = Object.entries(value);
371
-
372
- // Handle multiple operators in $not
373
- if (entries.length > 0) {
374
- // If all entries are operators, handle them as a single condition
375
- if (entries.every(([op]) => this.isOperator(op))) {
376
- const translatedCondition = this.translateFieldConditions(field, value);
377
- return {
378
- bool: {
379
- must_not: [translatedCondition],
380
- },
381
- };
382
- }
383
-
384
- // Handle single nested operator
385
- if (entries.length === 1 && entries[0] && this.isOperator(entries[0][0])) {
386
- const [nestedOp, nestedVal] = entries[0] as [QueryOperator, any];
387
- const translatedNested = this.translateFieldOperator(field, nestedOp, nestedVal);
388
- return {
389
- bool: {
390
- must_not: [translatedNested],
391
- },
392
- };
393
- }
394
- }
395
- }
396
- return this.translateLogicalOperator(operator, value);
397
- }
398
-
399
- // If a field is provided, use translateFieldOperator for more specific translation
400
- if (field) {
401
- return this.translateFieldOperator(field, operator, value);
402
- }
403
-
404
- // For non-logical operators without a field context, just return the value
405
- // The actual translation happens in translateFieldConditions where we have the field context
406
- return value;
407
- }
408
-
409
- /**
410
- * Translates field conditions to OpenSearch query syntax
411
- * Handles special cases like range queries and multiple operators
412
- */
413
- private translateFieldConditions(field: string, conditions: Record<string, any>): any {
414
- // Special case: Optimize multiple numeric operators into a single range query
415
- if (this.canOptimizeToRangeQuery(conditions)) {
416
- return this.createRangeQuery(field, conditions);
417
- }
418
-
419
- // Handle all other operators consistently
420
- const queryConditions: any[] = [];
421
- Object.entries(conditions).forEach(([operator, value]) => {
422
- if (this.isOperator(operator)) {
423
- queryConditions.push(this.translateOperator(operator as QueryOperator, value, field));
424
- } else {
425
- // Handle non-operator keys (should not happen in normal usage)
426
- const fieldWithKeyword = this.addKeywordIfNeeded(`${field}.${operator}`, value);
427
- queryConditions.push({ term: { [fieldWithKeyword]: value } });
428
- }
429
- });
430
-
431
- // Return single condition without wrapping
432
- if (queryConditions.length === 1) {
433
- return queryConditions[0];
434
- }
435
-
436
- // Combine multiple conditions with AND logic
437
- return {
438
- bool: {
439
- must: queryConditions,
440
- },
441
- };
442
- }
443
-
444
- /**
445
- * Checks if conditions can be optimized to a range query
446
- */
447
- private canOptimizeToRangeQuery(conditions: Record<string, any>): boolean {
448
- return Object.keys(conditions).every(op => this.isNumericOperator(op)) && Object.keys(conditions).length > 0;
449
- }
450
-
451
- /**
452
- * Creates a range query from numeric operators
453
- */
454
- private createRangeQuery(field: string, conditions: Record<string, any>): any {
455
- const rangeParams = Object.fromEntries(
456
- Object.entries(conditions).map(([op, val]) => [op.replace('$', ''), this.normalizeComparisonValue(val)]),
457
- );
458
-
459
- return { range: { [field]: rangeParams } };
460
- }
461
- }