@maximai/maxim-js
Version:
Maxim AI JS SDK. Visit https://getmaxim.ai for more info.
172 lines • 7.71 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseIncomingQuery = parseIncomingQuery;
exports.findBestMatch = findBestMatch;
exports.findAllMatches = findAllMatches;
// Function to parse the incoming query into a format compatible with RuleType
function parseIncomingQuery(incomingQuery) {
if (incomingQuery.trim().length === 0) {
return [];
}
const operators = ["!=", ">=", "<=", ">", "<", "includes", "does not include", "="]; // Ensure longer operators come first
// Split only using commas that are not inside square brackets
return incomingQuery.split(/,(?![^[\]]*\])/).map((condition) => {
for (let op of operators) {
if (condition.includes(op)) {
let [field, value] = condition.split(op).map((s) => s.trim());
const operator = op;
let exactMatch = false;
if (field.startsWith("!!")) {
exactMatch = true;
field = field.slice(2);
}
// Here we will auto-parse the number values
if (!isNaN(Number(value))) {
return { field, value: Number(value), operator, exactMatch };
}
try {
const parsedValue = JSON.parse(value);
if (Array.isArray(parsedValue) && parsedValue.length > 0 && typeof parsedValue[0] === "string") {
return { field, value: parsedValue, operator, exactMatch };
}
}
catch (error) { }
return { field: field, value, operator, exactMatch };
}
}
throw new Error(`Unsupported operator found in condition "${condition}"`);
});
}
// Recursive function to evaluate rule groups against incoming query rules
// TODO this flow is not optimized for nested queries
function evaluateRuleGroup(ruleGroup, incomingQueryRules) {
// This will keep track of matched fields to be used for exact match
const matchedRules = [];
const matchResults = ruleGroup.rules.map((rule) => {
if ("combinator" in rule) {
// It's a nested rule group
return evaluateRuleGroup(rule, incomingQueryRules);
}
else {
// It's a regular rule
return incomingQueryRules.some((incomingRule) => {
const conditionMet = (fieldRule, fieldIncomingRule) => {
// Checking for the type of the value
if (typeof fieldRule.value !== typeof fieldIncomingRule.value) {
// Here we will honor the type of the value in the rule
// And try to cast incoming rule to that value
if (typeof fieldRule.value === "number") {
fieldIncomingRule.value = Number(fieldIncomingRule.value);
}
else if (typeof fieldRule.value === "boolean") {
fieldIncomingRule.value = Boolean(fieldIncomingRule.value);
}
else if (typeof fieldRule.value === "string") {
fieldIncomingRule.value = String(fieldIncomingRule.value);
}
else if (Array.isArray(fieldRule.value)) {
try {
fieldIncomingRule.value = JSON.parse(fieldIncomingRule.value);
}
catch (error) { }
}
}
switch (fieldRule.operator) {
case "=":
if (Array.isArray(fieldRule.value)) {
return JSON.stringify(fieldRule.value) === JSON.stringify(fieldIncomingRule.value);
}
return fieldRule.value === fieldIncomingRule.value;
case "!=":
return fieldRule.value !== fieldIncomingRule.value;
case ">":
return fieldRule.value > fieldIncomingRule.value;
case "<":
return fieldRule.value < fieldIncomingRule.value;
case ">=":
return fieldRule.value >= fieldIncomingRule.value;
case "<=":
return fieldRule.value <= fieldIncomingRule.value;
case "includes":
if (Array.isArray(fieldIncomingRule.value) && Array.isArray(fieldRule.value)) {
return fieldIncomingRule.value.every(el => fieldRule.value.includes(el));
}
return fieldRule.value.includes(fieldIncomingRule.value);
case "does not include":
return !fieldRule.value.includes(fieldIncomingRule.value);
}
return false;
};
const result = rule.field === incomingRule.field && conditionMet(rule, incomingRule);
if (result) {
matchedRules.push(incomingRule);
}
return result;
});
}
});
// Here we will check if every exact match rule is matched
// If not, we will return false
const exactMatches = incomingQueryRules.every((rule) => {
if (!rule.exactMatch) {
return true;
}
if (matchedRules.includes(rule)) {
return true;
}
return false;
});
if (!exactMatches) {
return false;
}
if (ruleGroup.combinator === "AND") {
return matchResults.every(Boolean);
}
else {
return matchResults.some(Boolean);
}
}
// Function to find the best match based on the incoming query
function findBestMatch(objects, incomingQuery) {
let bestMatch = null;
let maxMatchCount = 0;
const incomingQueryRules = parseIncomingQuery(incomingQuery.query);
for (const object of objects) {
const isMatch = evaluateRuleGroup(object.query, incomingQueryRules);
if (isMatch) {
if (incomingQuery.exactMatch) {
// Make all fields in the incomingQueryRule are matching with the object.query
if (incomingQueryRules.length !== object.query.rules.length) {
continue;
}
}
const matchCount = object.query.rules.length;
// Assume the first match found is the best match for simplicity
if (matchCount > maxMatchCount) {
maxMatchCount = matchCount;
bestMatch = object;
}
}
}
return bestMatch;
}
// Function to find the best match based on the incoming query
function findAllMatches(objects, incomingQuery) {
let bestMatch = null;
const matches = [];
const incomingQueryRules = parseIncomingQuery(incomingQuery.query);
for (const object of objects) {
const isMatch = evaluateRuleGroup(object.query, incomingQueryRules);
if (isMatch) {
if (incomingQuery.exactMatch) {
// Make all fields in the incomingQueryRule are matching with the object.query
if (incomingQueryRules.length !== object.query.rules.length) {
continue;
}
}
matches.push(object);
}
}
return matches;
}
//# sourceMappingURL=filterObjects.js.map