rulepilot
Version:
Rule parsing engine for JSON rules
919 lines (918 loc) • 48.3 kB
JavaScript
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Introspector_instances, _Introspector_helper, _Introspector_objectDiscovery, _Introspector_reverseNoneToAll, _Introspector_removeIrrelevantConstraints, _Introspector_flipConstraintOperator, _Introspector_asArray, _Introspector_introspectConditions, _Introspector_appendResult, _Introspector_removeItem;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Introspector = void 0;
const logger_1 = require("./logger");
const object_discovery_1 = require("./object-discovery");
const rule_helper_1 = require("./rule-helper");
/**
* Introspection deals with the process of examining the constraints and conditions of a rule to determine all the
* possible range of input criteria which would satisfy the rule along with the result of the rule that would be
* produced by the criteria.
*/
class Introspector {
constructor() {
_Introspector_instances.add(this);
_Introspector_helper.set(this, new rule_helper_1.RuleHelper());
_Introspector_objectDiscovery.set(this, new object_discovery_1.ObjectDiscovery());
/**
* Test Routine:
* We check each test item against the entire list of results.
* The test should be an AND format, this means that the test item AND each list item must be possible
*
* If the current type is “any” -> we just append the items
* If the current type is “all” -> we need to test the items against each other and all need to pass for them to be added.
*
* When merging up:
* If the parent type is “any”
* current type “any” -> we can just append the items
* current type “all” -> we can just append the items
* If the parent type is “all”
* current type “any” -> We need to test each item against the parent set, if any passes we add it in. The ones that fail do not get added. If none pass we stop
* current type “all” -> We need to test each item against the parent set, all must pass and all get added or none get added. If all do not pass we stop
*
* When to stop:
* If parent is null
* “all” None pass STOP
* “any” None pass do not stop
*
* If parent is any
* “all” None pass do not stop
* “any” None pass do not stop
*
* If parent is all
* “all” None pass STOP
* “any” None pass STOP
*
* Algorithm:
* -> Prepare all constraints as array
* -> Prepare all conditions as array
* -> Sort the array to push ‘all’ conditions to the start
* -> Check constraints first
* -> If parent is NULL
* -> if type is any
* -> add const fields/values to subject results (append)
* -> if type is all
* -> group const by field (and foreach group)
* -> test each const does not breach local group results or subject value
* -> if it passes
* -> add to local group results
* -> if fails
* -> empty the local/global results for all subjects
* -> stop processing any conditions under this node.
* -> if all pass send local to global
*
* -> If parent is any
* -> if type is any
* we continue adding const fields/values to subject results (append)
* -> if type is all
* -> group const by field (and foreach group)
* -> test each const does not breach local group results or subject value
* -> if it passes
* -> add to local group results
* -> if fails
* -> empty the local results for all subjects
* -> stop processing any conditions under this node.
* -> do not empty global results.
* -> if some pass (all will pass)
* -> add local to global results
*
* -> If parent is all
* -> if type is any
* -> group const by field (and foreach group)
* -> bal group results or subject value
* -> if passes
* -> add to global group results
* -> if fails
* -> do not add
* -> if all fail
* -> empty the local/global results for all subjects
* -> stop processing any conditions under this node.
* -> if type is all
* -> group const by field (and foreach group)
* -> test each const does not breach local group results or subject value
* -> if it passes
* -> add to local group results
* -> test against global results
* -> if it passes
* -> add to global group results
* -> if fails
* -> empty the global results for all subjects
* -> stop processing any conditions under this node.
* -> if fails
* -> empty the global results for all subjects
* -> stop processing any conditions under this node.
*
* -> Check conditions array
* -> recurse each condition
*/
}
/**
* Returns the number of outcomes that a rule has.
* @param rule The rule to check.
*/
numOutcomes(rule) {
// If the rule is not granular, we can only have one outcome
if (__classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isGranular(rule))
return 1;
// We need to find the number of `result` properties in the rule
// along with any `default` property.
// Prepare to check
let num = "default" in rule ? 1 : 0;
const conditions = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, rule.conditions);
for (const condition of conditions) {
if (__classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isConditionWithResult(condition)) {
num += 1;
continue;
}
const items = __classPrivateFieldGet(this, _Introspector_helper, "f").extractSubRules(condition);
// Check if any sub-rule has a result property.
num += items.reduce((prev, curr) => {
return __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isConditionWithResult(curr.subRule)
? prev + 1
: prev;
}, 0);
}
return num;
}
/**
* Given a rule, checks the constraints and conditions to determine the possible range of input criteria which would be
* satisfied by the rule.
* @param rule The rule to introspect.
* @param criteria The criteria to introspect against.
* @param subjects The subjects to introspect for.
*/
introspect(rule, criteria, subjects) {
// We care about all the possible values for the subjects which will satisfy
// the rule if the rule is tested against the criteria provided.
var _a, _b;
// To proceed we must first clone the rule (to avoid modifying the original)
rule = JSON.parse(JSON.stringify(rule));
// First step is to simplify the rule:
// 1. Make sure the rule conditions is an array.
// 2. Convert any 'none' conditions to an 'all' and reverse operators of all children (recursively).
// 3. Remove all constraints which are not relevant to the subjects provided.
rule.conditions = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, rule.conditions);
for (let i = 0; i < rule.conditions.length; i++) {
rule.conditions[i] = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_reverseNoneToAll).call(this, rule.conditions[i]);
rule.conditions[i] = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeIrrelevantConstraints).call(this, rule.conditions[i], [...subjects, ...criteria.map((c) => c.field)]);
}
// We then need to extract all sub-rules from the main rule
let subRules = [];
for (const condition of rule.conditions) {
subRules = subRules.concat(__classPrivateFieldGet(this, _Introspector_helper, "f").extractSubRules(condition));
}
// We then create a new version of the rule without any of the sub-rules
const conditions = [];
for (let i = 0; i < rule.conditions.length; i++) {
conditions.push(__classPrivateFieldGet(this, _Introspector_helper, "f").removeAllSubRules(rule.conditions[i]));
}
// Take each sub-rule we extracted and create a new condition which joins the sub-rule with the
// parent condition(s) it must satisfy. We then add this new condition to the list of conditions.
subRules.forEach((rule) => {
var _a, _b;
if (!rule.parent) {
conditions.push(rule.subRule);
return;
}
const result = (_b = (_a = rule.subRule) === null || _a === void 0 ? void 0 : _a.result) !== null && _b !== void 0 ? _b : "default";
delete rule.parent.result;
delete rule.subRule.result;
conditions.push({ all: [rule.parent, rule.subRule], result });
});
// console.log("subRules", JSON.stringify(subRules));
// console.log("conditions", JSON.stringify(conditions));
// At this point the search becomes as follows: What are the possible values for the
// subjects which will satisfy the rule if the rule is tested against the constraint provided.
const results = [];
// We introspect the conditions to determine the possible values for the subjects
for (const condition of conditions.filter(Boolean)) {
const { values } = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_introspectConditions).call(this, condition, criteria);
if (!values)
continue;
const key = (_a = condition.result) !== null && _a !== void 0 ? _a : "default";
const map = new Map();
// Merge the results maintaining the uniqueness of the values
for (const [field, constraints] of values.entries()) {
if (!subjects.includes(field))
continue;
const temp = (_b = map.get(field)) !== null && _b !== void 0 ? _b : [];
if (!temp.length)
map.set(field, temp);
for (const constraint of constraints) {
temp.push({ value: constraint.value, operator: constraint.operator });
}
}
const s = [];
for (const [subject, values] of map.entries()) {
if (!values.length)
continue;
s.push({ subject, values });
}
if (s.length)
results.push({ result: key, subjects: s });
}
return results;
}
/**
* Given a list of valid candidates and the input used in the introspection, this method
* tests a new constraint (item) returning true if the item is self-consistent with the
* candidates and the input.
*
* Testing happens by considering each item in the list as linked by an AND
* @param candidates The result candidates to test against.
* @param criteria The constraint which was input to the introspection.
* @param item The constraint item to test against the candidates.
* @param depth The current recursion depth.
*/
test(candidates, criteria, item, depth) {
// Filter out results which do not match the field of the constraint
candidates = candidates.filter((r) => r.field === item.field);
// Add the input constraint to the results (if it also matches the field)
criteria.forEach((c) => {
if (c.field === item.field)
candidates.push(Object.assign(Object.assign({}, c), { operator: "==" }));
});
// Test that the constraint does not breach the results
// The test constraint needs to be compared against all the results
// and has to pass all the tests to be considered valid
let result = true;
for (const c of candidates) {
let ops;
// Extract the item properties to test with
const { value, operator } = item;
switch (c.operator) {
case "==":
/**
* c = (L == 500)
* L == 500
* L != !500
* L >= 500
* L <= 500
* L > 499
* L < 501
* L IN [500]
* L NOT IN [501, 502]
*/
// Must be equal to the value
if ("==" === operator && value !== c.value)
result = false;
// Item value must allow for constraint value to exist in item value range
if ("<=" === operator && value < c.value)
result = false;
if (">=" === operator && value > c.value)
result = false;
// Item value must allow for constraint value to exist in item value range
if ("<" === operator && value <= c.value)
result = false;
if (">" === operator && value >= c.value)
result = false;
// Item value cannot be equal to constraint value
if ("!=" === operator && value === c.value)
result = false;
// One of the values in the item must match the candidate value
if ("in" === operator) {
if (!__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val === c.value))
result = false;
}
// None of the values in the item must match the candidate value
if ("not in" === operator) {
if (__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val === c.value))
result = false;
}
break;
case "!=":
/**
* c = (L != 500)
* L == !500
* L != any
* L >= any
* L <= any
* L > any
* L < any
* L IN [500, 502]
* L NOT IN [499,500]
*/
// Must be different
if ("==" === operator && value === c.value)
result = false;
// // Always pass
// ops = ["!=", ">", ">=", "<", "<=", "not in"];
// if (ops.includes(operator)) result = true;
// One of the values in the item must NOT match the candidate value
if ("in" === operator) {
if (!__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val !== c.value))
result = false;
}
break;
case ">":
/**
* c = (L > 500)
* L == 501↑
* L != any
* L >= any
* L <= 501↑
* L > any
* L < 502↑
* IN [501, 502]
* NOT IN [501, 502]
*/
// Always pass ["!=", ">", ">=", "not in"]
// Must be bigger than the value
ops = ["==", "<="];
if (ops.includes(operator) && value <= c.value)
result = false;
if ("<" === operator && Number(value) <= Number(c.value) + 2)
result = false;
// One of the values in the item must match the candidate value
if ("in" === operator) {
if (!__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val > c.value))
result = false;
}
break;
case "<":
/**
* c = (L < 500)
* L == 499↓
* L != any
* L >= 499↓
* L <= any
* L > 498↓
* L < any
* IN [499, 500]
*/
// Always pass ["!=", "<", "<=", "not in"]
// Must be smaller than the value
ops = ["==", ">="];
if (ops.includes(operator) && value >= c.value)
result = false;
if (">" === operator && Number(value) >= Number(c.value) - 2)
result = false;
// One of the values in the item must match the candidate value
if ("in" === operator) {
if (!__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val < c.value))
result = false;
}
break;
case ">=":
/**
* c = (L >= 500)
* L == 500↑
* L != any
* L >= any
* L <= 500↑
* L > any
* L < 501↑
* L IN [500, 501]
*/
// Always pass ["!=", ">=", ">", "not in"]
// Must be bigger than the value
ops = ["==", "<="];
if (ops.includes(operator) && value < c.value)
result = false;
if ("<" === operator && Number(value) < Number(c.value) + 1)
result = false;
// One of the values in the item must match the candidate value
if ("in" === operator) {
if (!__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val >= c.value))
result = false;
}
break;
case "<=":
/**
* c = (L <= 500)
* L == 500↓
* L != any
* L >= 500↓
* L <= any
* L > 499↓
* L < any
*/
// Always pass ["!=", "<=", "<", "not in"]
// Must be smaller than the value
ops = ["==", ">="];
if (ops.includes(operator) && value > c.value)
result = false;
if (">" === operator && Number(value) > Number(c.value) - 1)
result = false;
// One of the values in the item must match the candidate value
if ("in" === operator) {
if (!__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => val <= c.value))
result = false;
}
break;
case "in":
/**
* c = (L [500,501)
* IN [500, 502]
* NOT IN [499, 500]
*/
result = false;
// At least 1 item from list must pass
// For each item run the same checks as for the '==' operator
for (const subVal of __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, c.value)) {
// Must be equal to the value
if ("==" === operator && value === subVal) {
result = true;
}
// Item value must allow for constraint value to exist in item value range
if ("<=" === operator && value >= subVal) {
result = true;
}
if (">=" === operator && value <= subVal) {
result = true;
}
// Item value must allow for constraint value to exist in item value range
if ("<" === operator && value > subVal) {
result = true;
}
if (">" === operator && value < subVal) {
result = true;
}
// Item value cannot be equal to constraint value
if ("!=" === operator && value !== subVal) {
result = true;
}
}
const inValues = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, c.value);
// One of the values in the item must match any candidate values
if ("in" === operator) {
if (__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => inValues.includes(val)))
result = true;
}
// One of the values in the item must NOT match any candidate values
if ("not in" === operator) {
if (__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => !inValues.includes(val)))
result = true;
}
break;
case "not in":
/**
* c = (L NOT IN [500,501)
* IN [499, 501]
* NOT IN [500, 499]
*/
result = true;
// All items from list must pass
for (const subVal of __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, c.value)) {
// Must be different
if ("==" === operator && value === subVal) {
result = false;
}
}
const notInValues = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, c.value);
// One of the values in the item must NOT match any candidate values
if ("in" === operator) {
result = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, value).some((val) => !notInValues.includes(val));
}
break;
// case "contains":
// break;
// case "not contains":
// break;
// case "contains any":
// break;
// case "not contains any":
// break;
// case "matches":
// break;
// case "not matches":
// break;
}
}
// Prepare the log
const gap = " ".repeat(depth);
const col = logger_1.Logger.color(item.field, "g");
const val = logger_1.Logger.color(item.value, "y");
const pass = logger_1.Logger.color("pass", "g");
const fail = logger_1.Logger.color("fail", "r");
const msg = ` ${gap}> Testing '${col}'${item.operator}'${val}'`;
logger_1.Logger.debug(msg, `(${result ? pass : fail})`);
// Return the result
return result;
}
/**
* Takes a list of constraints which represent the possible values for a field which satisfy a rule
* and sanitizes the list to remove any constraints which are redundant. This method will convert the
* list of constraints to the smallest possible list of constraints which still represent the same
* possible values for the field.
*
* Sanitization happens by considering each item in the list as linked by an OR
* @param constraints The constraints to sanitize.
* @param depth The current recursion depth.
*/
sanitize(constraints, depth) {
// Create a results list which we can modify
let results = JSON.parse(JSON.stringify(constraints));
// Clone the constraints so that we can modify them in needed
for (const sub of JSON.parse(JSON.stringify(constraints))) {
const op = sub.operator;
const val = sub.value;
// Clone the list and iterate again
for (const c of JSON.parse(JSON.stringify(constraints))) {
// If the clone and the subject are the same, skip
if (JSON.stringify(c) === JSON.stringify(sub))
continue;
if (">=" === op) {
if ("==" === c.operator && c.value === val) {
return this.sanitize(__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results), depth);
}
if (">" === c.operator) {
// >=500, >500+ (remove >500+)
if (c.value >= val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results);
// >=500, >499- (remove >=500)
if (c.value < val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results);
return this.sanitize(results, depth);
}
if (">=" === c.operator) {
// >=500, >=500+ (remove >=500+)
if (c.value >= val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results);
// >=500, >=499- (remove >=500)
if (c.value < val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results);
return this.sanitize(results, depth);
}
}
if ("<=" === op) {
if ("==" === c.operator && c.value === val) {
return this.sanitize(__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results), depth);
}
if ("<" === c.operator) {
// <=500, <500- (remove <500-)
if (c.value <= val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results);
// <=500, <501+ (remove >=500)
if (c.value > val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results);
return this.sanitize(results, depth);
}
if ("<=" === c.operator) {
// <=500, <500- (remove <500-)
if (c.value <= val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results);
// <=500, <501+ (remove >=500)
if (c.value > val)
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results);
return this.sanitize(results, depth);
}
}
if (">" === op) {
if ("==" === c.operator && c.value > val) {
return this.sanitize(__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results), depth);
}
if ("==" === c.operator && c.value === val) {
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results));
results.push(Object.assign(Object.assign({}, sub), { operator: ">=" }));
return this.sanitize(results, depth);
}
if (">" === c.operator && c.value >= val) {
return this.sanitize(__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results), depth);
}
}
if ("<" === op) {
if ("==" === c.operator && c.value < val) {
return this.sanitize(__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results), depth);
}
if ("==" === c.operator && c.value === val) {
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results));
results.push(Object.assign(Object.assign({}, sub), { operator: "<=" }));
return this.sanitize(results, depth);
}
if ("<" === c.operator && c.value <= val) {
return this.sanitize(__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, results), depth);
}
}
if ("in" === op) {
// We join the two lists and remove the duplicates
if ("in" === c.operator) {
const set = new Set([
...__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, val),
...__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, c.value),
]);
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results));
results.push(Object.assign(Object.assign({}, sub), { value: [...set].sort() }));
return this.sanitize(results, depth);
}
}
if ("not in" === op) {
// We join the two lists and remove the duplicates
if ("not in" === c.operator) {
const set = new Set([
...__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, val),
...__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, c.value),
]);
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, c, __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results));
results.push(Object.assign(Object.assign({}, sub), { value: [...set].sort() }));
return this.sanitize(results, depth);
}
}
}
// If the list has 1 item, we convert to ==
if (["in"].includes(op)) {
if (1 === __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, sub.value).length) {
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results);
results.push({ field: sub.field, operator: "==", value: val });
return this.sanitize(results, depth);
}
}
// If the list has 1 item, we convert to !=
if (["not in"].includes(op)) {
if (1 === __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_asArray).call(this, sub.value).length) {
results = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeItem).call(this, sub, results);
results.push({ field: sub.field, operator: "!=", value: val });
return this.sanitize(results, depth);
}
}
}
const gap = " ".repeat(depth);
const msg = ` ${gap}${logger_1.Logger.color(`* Sanitized`, "b")}`;
const values = results.map((c) => `${c.operator}${logger_1.Logger.color(c.value, "m")}`);
logger_1.Logger.debug(`${msg} [${values.join(", ")}]`);
return results;
}
}
exports.Introspector = Introspector;
_Introspector_helper = new WeakMap(), _Introspector_objectDiscovery = new WeakMap(), _Introspector_instances = new WeakSet(), _Introspector_reverseNoneToAll = function _Introspector_reverseNoneToAll(condition, shouldFlip = false) {
const type = __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").conditionType(condition);
if ("none" === type)
shouldFlip = !shouldFlip;
// Iterate each node in the condition
for (let i = 0; i < condition[type].length; i++) {
let node = condition[type][i];
// If the node is a condition, check if we need to reverse it
if (shouldFlip && __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isConstraint(node)) {
node = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_flipConstraintOperator).call(this, node);
}
// If the node is a condition, recurse
if (__classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isCondition(node)) {
node = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_reverseNoneToAll).call(this, node, shouldFlip);
}
}
if (shouldFlip) {
if ("none" === type) {
condition["all"] = condition[type];
delete condition[type];
}
if ("any" === type) {
condition["all"] = condition[type];
delete condition[type];
}
}
return __classPrivateFieldGet(this, _Introspector_helper, "f").stripNullProps(condition);
}, _Introspector_removeIrrelevantConstraints = function _Introspector_removeIrrelevantConstraints(condition, toKeep) {
const type = __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").conditionType(condition);
// Iterate each node in the condition
for (let i = 0; i < condition[type].length; i++) {
let node = condition[type][i];
// If the node is a condition, check if we need to reverse it
const isConstraint = __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isConstraint(node);
if (isConstraint && !toKeep.includes(node["field"])) {
delete condition[type][i];
}
// If the node is a condition, recurse
if (__classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isCondition(node)) {
node = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_removeIrrelevantConstraints).call(this, node, toKeep);
}
}
return __classPrivateFieldGet(this, _Introspector_helper, "f").stripNullProps(condition);
}, _Introspector_flipConstraintOperator = function _Introspector_flipConstraintOperator(c) {
if ("==" === c.operator) {
c.operator = "!=";
return c;
}
if ("!=" === c.operator) {
c.operator = "==";
return c;
}
if (">" === c.operator) {
c.operator = "<=";
return c;
}
if ("<" === c.operator) {
c.operator = ">=";
return c;
}
if (">=" === c.operator) {
c.operator = "<";
return c;
}
if ("<=" === c.operator) {
c.operator = ">";
return c;
}
if ("in" === c.operator) {
c.operator = "not in";
return c;
}
if ("not in" === c.operator) {
c.operator = "in";
return c;
}
if ("contains" === c.operator) {
c.operator = "not contains";
return c;
}
if ("not contains" === c.operator) {
c.operator = "contains";
return c;
}
if ("contains any" === c.operator) {
c.operator = "not contains any";
return c;
}
if ("not contains any" === c.operator) {
c.operator = "contains any";
return c;
}
if ("matches" === c.operator) {
c.operator = "not matches";
return c;
}
if ("not matches" === c.operator) {
c.operator = "matches";
return c;
}
return c;
}, _Introspector_asArray = function _Introspector_asArray(value) {
return Array.isArray(value) ? value : [value];
}, _Introspector_introspectConditions = function _Introspector_introspectConditions(condition, criteria, parentType = null, parentResults = new Map(), depth = 0) {
var _a, _b, _c;
// Prepare the lists
const conditions = [];
const groupedConst = new Map();
// Iterate over each node in the condition
const type = __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").conditionType(condition);
for (const node of condition[type]) {
if (__classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isCondition(node)) {
// Process the 'all' conditions before the 'any' conditions
if ("all" === __classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").conditionType(node))
conditions.unshift(node);
else
conditions.push(node);
}
if (__classPrivateFieldGet(this, _Introspector_objectDiscovery, "f").isConstraint(node)) {
__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_appendResult).call(this, groupedConst, node);
}
}
const gap = " ".repeat(depth);
const msg = 0 === depth
? `\nIntrospecting "${logger_1.Logger.bold(type)}" condition`
: `${gap}--> "${logger_1.Logger.bold(type)}" condition`;
logger_1.Logger.debug(msg);
// Prepare the local results
let clearLocal = false;
let results = new Map();
// Iterate over all grouped constraints
for (const [field, constraints] of groupedConst.entries()) {
// Prepare the candidates for this field type
let candidates = (_a = results.get(field)) !== null && _a !== void 0 ? _a : [];
if (clearLocal)
continue;
// Test the constraints and prepare the local results
////////////////////////////////////////////////////////////
for (const c of constraints) {
// Append to local results
if ("any" === type) {
const col = logger_1.Logger.color(c.field, "g");
const val = logger_1.Logger.color(c.value, "y");
const msg = ` ${gap}+ Adding local '${col}'${c.operator}'${val}'`;
logger_1.Logger.debug(msg, `(${logger_1.Logger.color("pass", "g")})`);
candidates.push(c);
}
if ("all" === type) {
if (!this.test(candidates, criteria, c, depth)) {
candidates = [];
clearLocal = true;
logger_1.Logger.debug(` ${gap}- Clearing local candidates...`);
break;
}
const col = logger_1.Logger.color(c.field, "g");
const val = logger_1.Logger.color(c.value, "y");
const msg = ` ${gap}+ Adding local '${col}'${c.operator}'${val}'`;
logger_1.Logger.debug(msg, `(${logger_1.Logger.color("pass", "g")})`);
candidates.push(c);
}
}
// Store the updated candidates into the local results
results.set(field, candidates);
}
if (clearLocal)
results = new Map();
////////////////////////////////////////////////////////////
// Merge the local results with the parent results
////////////////////////////////////////////////////////////
if (null === parentType) {
for (const [_, candidates] of results.entries()) {
for (const c of candidates)
__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_appendResult).call(this, parentResults, c);
}
}
if ("any" === parentType) {
if ("any" === type) {
for (const [_, candidates] of results.entries()) {
for (const c of candidates)
__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_appendResult).call(this, parentResults, c);
}
}
if ("all" === type) {
for (const [_, candidates] of results.entries()) {
if (!candidates.length) {
logger_1.Logger.debug(`${gap}X Exiting...`);
return { values: parentResults, stop: true, void: false };
}
for (const c of candidates)
__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_appendResult).call(this, parentResults, c);
}
}
}
if ("all" === parentType) {
if ("any" === type) {
const valid = [];
for (const [field, candidates] of results.entries()) {
for (const c of candidates) {
const parentRes = (_b = parentResults.get(field)) !== null && _b !== void 0 ? _b : [];
if (this.test(parentRes, criteria, c, depth))
valid.push(c);
}
}
if (!valid.length) {
logger_1.Logger.debug(`${gap}${logger_1.Logger.color("Exiting & Discarding results...", "r")}`);
return { values: parentResults, stop: true, void: true };
}
for (const c of valid)
__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_appendResult).call(this, parentResults, c);
}
if ("all" === type) {
// We assume all constraints are valid until proven otherwise,
// However if the list is empty we must say that no constraint has passed.
let allPass = Array.from(results.values()).flat().length > 0;
for (const [field, candidates] of results.entries()) {
for (const c of candidates) {
const parentRes = (_c = parentResults.get(field)) !== null && _c !== void 0 ? _c : [];
if (!this.test(parentRes, criteria, c, depth))
allPass = false;
}
}
if (!allPass) {
logger_1.Logger.debug(`${gap}${logger_1.Logger.color("Exiting & Discarding results...", "r")}`);
return { values: parentResults, stop: true, void: true };
}
for (const [_, candidates] of results.entries()) {
for (const c of candidates) {
__classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_appendResult).call(this, parentResults, c);
}
}
}
}
////////////////////////////////////////////////////////////
// Log the results
////////////////////////////////////////////////////////////
for (const [k, v] of parentResults.entries()) {
const values = [];
for (const c of v) {
values.push(`${c.operator}${logger_1.Logger.color(c.value, "y")}`);
}
const msg = ` ${gap}${logger_1.Logger.color("* Candidates", "m")} `;
logger_1.Logger.debug(`${msg}${logger_1.Logger.color(k, "g")}: [${values.join(", ")}]`);
}
////////////////////////////////////////////////////////////
// Sanitize the results
////////////////////////////////////////////////////////////
for (const [field, constraints] of parentResults.entries()) {
parentResults.set(field, this.sanitize(constraints, depth));
}
////////////////////////////////////////////////////////////
// Iterate over all conditions
////////////////////////////////////////////////////////////
for (const c of conditions) {
// Introspect the condition and append the results to the parent results
const d = depth + 1;
const res = __classPrivateFieldGet(this, _Introspector_instances, "m", _Introspector_introspectConditions).call(this, c, criteria, type, parentResults, d);
if (res.void)
parentResults = new Map();
else
parentResults = res.values;
if (res.stop)
return { values: parentResults, stop: res.stop, void: res.void };
}
//////////////////////////////////////////
return { values: parentResults, stop: false, void: false };
}, _Introspector_appendResult = function _Introspector_appendResult(map, c) {
var _a;
const temp = (_a = map.get(c.field)) !== null && _a !== void 0 ? _a : [];
map.set(c.field, temp);
// Do not add duplicate constraints
if (temp.some((t) => JSON.stringify(t) === JSON.stringify(c)))
return;
temp.push(c);
}, _Introspector_removeItem = function _Introspector_removeItem(needle, haystack) {
return haystack.filter((r) => JSON.stringify(r) !== JSON.stringify(needle));
};