@mastra/core
Version:
The core foundation of the Mastra framework, providing essential components and interfaces for building AI-powered applications.
191 lines (189 loc) • 6.96 kB
JavaScript
// src/vector/filter/base.ts
var BaseFilterTranslator = class _BaseFilterTranslator {
/**
* Operator type checks
*/
isOperator(key) {
return key.startsWith("$");
}
static BASIC_OPERATORS = ["$eq", "$ne"];
static NUMERIC_OPERATORS = ["$gt", "$gte", "$lt", "$lte"];
static ARRAY_OPERATORS = ["$in", "$nin", "$all", "$elemMatch"];
static LOGICAL_OPERATORS = ["$and", "$or", "$not", "$nor"];
static ELEMENT_OPERATORS = ["$exists"];
static REGEX_OPERATORS = ["$regex", "$options"];
static DEFAULT_OPERATORS = {
logical: _BaseFilterTranslator.LOGICAL_OPERATORS,
basic: _BaseFilterTranslator.BASIC_OPERATORS,
numeric: _BaseFilterTranslator.NUMERIC_OPERATORS,
array: _BaseFilterTranslator.ARRAY_OPERATORS,
element: _BaseFilterTranslator.ELEMENT_OPERATORS,
regex: _BaseFilterTranslator.REGEX_OPERATORS
};
isLogicalOperator(key) {
return _BaseFilterTranslator.DEFAULT_OPERATORS.logical.includes(key);
}
isBasicOperator(key) {
return _BaseFilterTranslator.DEFAULT_OPERATORS.basic.includes(key);
}
isNumericOperator(key) {
return _BaseFilterTranslator.DEFAULT_OPERATORS.numeric.includes(key);
}
isArrayOperator(key) {
return _BaseFilterTranslator.DEFAULT_OPERATORS.array.includes(key);
}
isElementOperator(key) {
return _BaseFilterTranslator.DEFAULT_OPERATORS.element.includes(key);
}
isRegexOperator(key) {
return _BaseFilterTranslator.DEFAULT_OPERATORS.regex.includes(key);
}
isFieldOperator(key) {
return this.isOperator(key) && !this.isLogicalOperator(key);
}
isCustomOperator(key) {
const support = this.getSupportedOperators();
return support.custom?.includes(key) ?? false;
}
getSupportedOperators() {
return _BaseFilterTranslator.DEFAULT_OPERATORS;
}
isValidOperator(key) {
const support = this.getSupportedOperators();
const allSupported = Object.values(support).flat();
return allSupported.includes(key);
}
/**
* Value normalization for comparison operators
*/
normalizeComparisonValue(value) {
if (value instanceof Date) {
return value.toISOString();
}
if (typeof value === "number" && Object.is(value, -0)) {
return 0;
}
return value;
}
/**
* Helper method to simulate $all operator using $and + $eq when needed.
* Some vector stores don't support $all natively.
*/
simulateAllOperator(field, values) {
return {
$and: values.map((value) => ({
[field]: { $in: [this.normalizeComparisonValue(value)] }
}))
};
}
/**
* Utility functions for type checking
*/
isPrimitive(value) {
return value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
isRegex(value) {
return value instanceof RegExp;
}
isEmpty(obj) {
return obj === null || obj === void 0 || typeof obj === "object" && Object.keys(obj).length === 0;
}
static ErrorMessages = {
UNSUPPORTED_OPERATOR: (op) => `Unsupported operator: ${op}`,
INVALID_LOGICAL_OPERATOR_LOCATION: (op, path) => `Logical operator ${op} cannot be used at field level: ${path}`,
NOT_REQUIRES_OBJECT: `$not operator requires an object`,
NOT_CANNOT_BE_EMPTY: `$not operator cannot be empty`,
INVALID_LOGICAL_OPERATOR_CONTENT: (path) => `Logical operators must contain field conditions, not direct operators: ${path}`,
INVALID_TOP_LEVEL_OPERATOR: (op) => `Invalid top-level operator: ${op}`,
ELEM_MATCH_REQUIRES_OBJECT: `$elemMatch requires an object with conditions`
};
/**
* Helper to handle array value normalization consistently
*/
normalizeArrayValues(values) {
return values.map((value) => this.normalizeComparisonValue(value));
}
validateFilter(filter) {
const validation = this.validateFilterSupport(filter);
if (!validation.supported) {
throw new Error(validation.messages.join(", "));
}
}
/**
* Validates if a filter structure is supported by the specific vector DB
* and returns detailed validation information.
*/
validateFilterSupport(node, path = "") {
const messages = [];
if (this.isPrimitive(node) || this.isEmpty(node)) {
return { supported: true, messages: [] };
}
if (Array.isArray(node)) {
const arrayResults = node.map((item) => this.validateFilterSupport(item, path));
const arrayMessages = arrayResults.flatMap((r) => r.messages);
return {
supported: arrayResults.every((r) => r.supported),
messages: arrayMessages
};
}
const nodeObj = node;
let isSupported = true;
for (const [key, value] of Object.entries(nodeObj)) {
const newPath = path ? `${path}.${key}` : key;
if (this.isOperator(key)) {
if (!this.isValidOperator(key)) {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.UNSUPPORTED_OPERATOR(key));
continue;
}
if (!path && !this.isLogicalOperator(key)) {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.INVALID_TOP_LEVEL_OPERATOR(key));
continue;
}
if (key === "$elemMatch" && (typeof value !== "object" || Array.isArray(value))) {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.ELEM_MATCH_REQUIRES_OBJECT);
continue;
}
if (this.isLogicalOperator(key)) {
if (key === "$not") {
if (Array.isArray(value) || typeof value !== "object") {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.NOT_REQUIRES_OBJECT);
continue;
}
if (this.isEmpty(value)) {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.NOT_CANNOT_BE_EMPTY);
continue;
}
continue;
}
if (path && !this.isLogicalOperator(path.split(".").pop())) {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.INVALID_LOGICAL_OPERATOR_LOCATION(key, newPath));
continue;
}
if (Array.isArray(value)) {
const hasDirectOperators = value.some(
(item) => typeof item === "object" && Object.keys(item).length === 1 && this.isFieldOperator(Object.keys(item)[0])
);
if (hasDirectOperators) {
isSupported = false;
messages.push(_BaseFilterTranslator.ErrorMessages.INVALID_LOGICAL_OPERATOR_CONTENT(newPath));
continue;
}
}
}
}
const nestedValidation = this.validateFilterSupport(value, newPath);
if (!nestedValidation.supported) {
isSupported = false;
messages.push(...nestedValidation.messages);
}
}
return { supported: isSupported, messages };
}
};
export { BaseFilterTranslator };