react-querybuilder
Version:
React Query Builder component for constructing queries and filters, with utilities for executing them in various database and evaluation contexts
584 lines (567 loc) • 22.3 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/utils/parseMongoDB/index.ts
var parseMongoDB_exports = {};
__export(parseMongoDB_exports, {
parseMongoDB: () => parseMongoDB
});
module.exports = __toCommonJS(parseMongoDB_exports);
// src/defaults.ts
var defaultJoinChar = ",";
var defaultOperatorNegationMap = {
"=": "!=",
"!=": "=",
"<": ">=",
"<=": ">",
">": "<=",
">=": "<",
beginsWith: "doesNotBeginWith",
doesNotBeginWith: "beginsWith",
endsWith: "doesNotEndWith",
doesNotEndWith: "endsWith",
contains: "doesNotContain",
doesNotContain: "contains",
between: "notBetween",
notBetween: "between",
in: "notIn",
notIn: "in",
notNull: "null",
null: "notNull"
};
var defaultCombinators = [
{ name: "and", value: "and", label: "AND" },
{ name: "or", value: "or", label: "OR" }
];
var defaultCombinatorsExtended = [
...defaultCombinators,
{ name: "xor", value: "xor", label: "XOR" }
];
// src/utils/arrayUtils.ts
var joinWith = (strArr, joinChar = defaultJoinChar) => strArr.map((str) => `${str ?? ""}`.replaceAll(joinChar[0], `\\${joinChar[0]}`)).join(joinChar);
// src/utils/convertQuery.ts
var import_immer = require("immer");
// src/utils/misc.ts
var import_numeric_quantity = require("numeric-quantity");
var lc = (v) => typeof v === "string" ? v.toLowerCase() : v;
var numericRegex = new RegExp(
import_numeric_quantity.numericRegex.source.replace(/^\^/, String.raw`^\s*`).replace(/\$$/, String.raw`\s*$`)
);
var isPojo = (obj) => obj === null || typeof obj !== "object" ? false : Object.getPrototypeOf(obj) === Object.prototype;
// src/utils/isRuleGroup.ts
var isRuleGroup = (rg) => isPojo(rg) && Array.isArray(rg.rules);
var isRuleGroupType = (rg) => isRuleGroup(rg) && typeof rg.combinator === "string";
var isRuleGroupTypeIC = (rg) => isRuleGroup(rg) && rg.combinator === void 0;
// src/utils/convertQuery.ts
var convertToIC = (rg) => {
if (isRuleGroupTypeIC(rg)) {
return rg;
}
const { combinator, ...queryWithoutCombinator } = rg;
const rules = [];
const { length } = rg.rules;
for (const [idx, r] of rg.rules.entries()) {
if (isRuleGroup(r)) {
rules.push(convertToIC(r));
} else {
rules.push(r);
}
if (combinator && idx < length - 1) {
rules.push(combinator);
}
}
return { ...queryWithoutCombinator, rules };
};
// src/utils/objectUtils.ts
var objectKeys = Object.keys;
// src/utils/optGroupUtils.ts
var import_immer2 = require("immer");
var isOptionWithName = (opt) => isPojo(opt) && "name" in opt && typeof opt.name === "string";
var isOptionWithValue = (opt) => isPojo(opt) && "value" in opt && typeof opt.value === "string";
function toFullOption(opt, baseProperties, labelMap) {
const recipe = (0, import_immer2.produce)((draft) => {
const idObj = {};
let needsUpdating = !!baseProperties;
if (typeof draft === "string") {
return {
...baseProperties,
name: draft,
value: draft,
label: labelMap?.[draft] ?? draft
};
}
if (isOptionWithName(draft) && !isOptionWithValue(draft)) {
idObj.value = draft.name;
needsUpdating = true;
} else if (!isOptionWithName(draft) && isOptionWithValue(draft)) {
idObj.name = draft.value;
needsUpdating = true;
}
if (needsUpdating) {
return Object.assign({}, baseProperties, draft, idObj);
}
});
return recipe(opt);
}
function toFullOptionList(optList, baseProperties, labelMap) {
if (!Array.isArray(optList)) {
return [];
}
const recipe = (0, import_immer2.produce)((draft) => {
if (isFlexibleOptionGroupArray(draft)) {
for (const optGroup of draft) {
for (const [idx, opt] of optGroup.options.entries())
optGroup.options[idx] = toFullOption(opt, baseProperties, labelMap);
}
} else {
for (const [idx, opt] of draft.entries())
draft[idx] = toFullOption(opt, baseProperties, labelMap);
}
});
return recipe(optList);
}
var uniqByIdentifier = (originalArray) => {
const names = /* @__PURE__ */ new Set();
const newArray = [];
for (const el of originalArray) {
if (!names.has(el.value ?? el.name)) {
names.add(el.value ?? el.name);
newArray.push(el);
}
}
return originalArray.length === newArray.length ? originalArray : newArray;
};
var isOptionGroupArray = (arr) => Array.isArray(arr) && arr.length > 0 && isPojo(arr[0]) && "options" in arr[0] && Array.isArray(arr[0].options);
var isFlexibleOptionArray = (arr) => {
let isFOA = false;
if (Array.isArray(arr)) {
for (const o of arr) {
if (isOptionWithName(o) || isOptionWithValue(o)) {
isFOA = true;
} else {
return false;
}
}
}
return isFOA;
};
var isFlexibleOptionGroupArray = (arr, { allowEmpty = false } = {}) => {
let isFOGA = false;
if (Array.isArray(arr)) {
for (const og of arr) {
if (isPojo(og) && "options" in og && (isFlexibleOptionArray(og.options) || allowEmpty && Array.isArray(og.options) && og.options.length === 0)) {
isFOGA = true;
} else {
return false;
}
}
}
return isFOGA;
};
var toFlatOptionArray = (arr) => uniqByIdentifier(isOptionGroupArray(arr) ? arr.flatMap((og) => og.options) : arr);
// src/utils/filterFieldsByComparator.ts
var filterByComparator = (field, operator, fieldToCompare) => {
const fullField = toFullOption(field);
const fullFieldToCompare = toFullOption(fieldToCompare);
if (fullField.value === fullFieldToCompare.value) {
return false;
}
if (typeof fullField.comparator === "string") {
return fullField[fullField.comparator] === fullFieldToCompare[fullField.comparator];
}
return fullField.comparator?.(fullFieldToCompare, operator) ?? /* istanbul ignore next */
false;
};
var filterFieldsByComparator = (field, fields, operator) => {
if (!field.comparator) {
const filterOutSameField = (f) => (f.value ?? /* istanbul ignore next */
f.name) !== (field.value ?? /* istanbul ignore next */
field.name);
if (isFlexibleOptionGroupArray(fields)) {
return fields.map((og) => ({
...og,
options: og.options.filter((v) => filterOutSameField(v))
}));
}
return fields.filter((v) => filterOutSameField(v));
}
if (isFlexibleOptionGroupArray(fields)) {
return fields.map((og) => ({
...og,
options: og.options.filter((f) => filterByComparator(field, operator, f))
})).filter((og) => og.options.length > 0);
}
return fields.filter((f) => filterByComparator(field, operator, f));
};
// src/utils/getValueSourcesUtil.ts
var defaultValueSourcesArray = [
{ name: "value", value: "value", label: "value" }
];
var dummyFD = {
name: "name",
value: "name",
valueSources: null,
label: "label"
};
var getValueSourcesUtil = (fieldData, operator, getValueSources) => {
const fd = fieldData ? toFullOption(fieldData) : (
/* istanbul ignore else */
dummyFD
);
let valueSourcesNEW = fd.valueSources ?? false;
if (typeof valueSourcesNEW === "function") {
valueSourcesNEW = valueSourcesNEW(operator);
}
if (!valueSourcesNEW && getValueSources) {
valueSourcesNEW = getValueSources(fd.value, operator, {
fieldData: fd
});
}
if (!valueSourcesNEW) {
return defaultValueSourcesArray;
}
if (isFlexibleOptionArray(valueSourcesNEW)) {
return toFullOptionList(valueSourcesNEW);
}
return valueSourcesNEW.map(
(vs) => defaultValueSourcesArray.find((dmm) => dmm.value === lc(vs)) ?? {
name: vs,
value: vs,
label: vs
}
);
};
// src/utils/parserUtils.ts
var getFieldsArray = (fields) => {
const fieldsArray = fields ? Array.isArray(fields) ? fields : Object.keys(fields).map((fld) => ({ ...fields[fld], name: fld })).sort((a, b) => a.label.localeCompare(b.label)) : [];
return toFlatOptionArray(fieldsArray);
};
function fieldIsValidUtil(params) {
const { fieldsFlat, fieldName, operator, subordinateFieldName, getValueSources } = params;
const vsIncludes = (vs) => {
const vss = getValueSourcesUtil(primaryField, operator, getValueSources);
return isFlexibleOptionArray(vss) && vss.some((vso) => vso.value === vs || vso.name === vs);
};
if (fieldsFlat.length === 0) return true;
let valid = false;
const primaryField = toFullOption(fieldsFlat.find((ff) => ff.name === fieldName));
if (primaryField) {
valid = !(!subordinateFieldName && operator !== "notNull" && operator !== "null" && !vsIncludes("value"));
if (valid && !!subordinateFieldName) {
if (vsIncludes("field") && fieldName !== subordinateFieldName) {
const validSubordinateFields = filterFieldsByComparator(
primaryField,
fieldsFlat,
operator
);
if (!validSubordinateFields.some((vsf) => vsf.name === subordinateFieldName)) {
valid = false;
}
} else {
valid = false;
}
}
}
return valid;
}
// src/utils/prepareQueryObjects.ts
var import_immer3 = require("immer");
// src/utils/generateID.ts
var cryptoModule = globalThis.crypto;
var generateID = () => "00-0-4-2-000".replaceAll(
/[^-]/g,
(s) => ((Math.random() + Math.trunc(s)) * 65536 >> Number.parseInt(s)).toString(16).padStart(4, "0")
);
if (cryptoModule) {
if (typeof cryptoModule.randomUUID === "function") {
generateID = () => cryptoModule.randomUUID();
} else if (typeof cryptoModule.getRandomValues === "function") {
const position19vals = "89ab";
const container = new Uint32Array(32);
generateID = () => {
cryptoModule.getRandomValues(container);
let id = (container[0] % 16).toString(16);
for (let i = 1; i < 32; i++) {
if (i === 12) {
id = `${id}${"4"}`;
} else if (i === 16) {
id = `${id}${position19vals[container[17] % 4]}`;
} else {
id = `${id}${(container[i] % 16).toString(16)}`;
}
if (i === 7 || i === 11 || i === 15 || i === 19) {
id = `${id}${"-"}`;
}
}
return id;
};
}
}
// src/utils/prepareQueryObjects.ts
var prepareRule = (rule, { idGenerator = generateID } = {}) => (0, import_immer3.produce)(rule, (draft) => {
if (!draft.id) {
draft.id = idGenerator();
}
});
var prepareRuleGroup = (queryObject, { idGenerator = generateID } = {}) => (0, import_immer3.produce)(queryObject, (draft) => {
if (!draft.id) {
draft.id = idGenerator();
}
draft.rules = draft.rules.map(
(r) => typeof r === "string" ? r : isRuleGroup(r) ? prepareRuleGroup(r, { idGenerator }) : prepareRule(r, { idGenerator })
);
});
// src/utils/parseMongoDB/utils.ts
var getRegExStr = (re) => typeof re === "string" ? re : re.source;
var isPrimitive = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
var mongoDbToRqbOperatorMap = {
$eq: "=",
$ne: "!=",
$gt: ">",
$gte: ">=",
$lt: "<",
$lte: "<="
};
// src/utils/parseMongoDB/parseMongoDB.ts
var emptyRuleGroup = { combinator: "and", rules: [] };
function parseMongoDB(mongoDbRules, options = {}) {
const listsAsArrays = !!options.listsAsArrays;
const fieldsFlat = getFieldsArray(options.fields);
const getValueSources = options.getValueSources;
const additionalOperators = options.additionalOperators ?? {};
const preventOperatorNegation = !!options.preventOperatorNegation;
const { additionalOperators: _ao, ...otherOptions } = options;
const fieldIsValid = (fieldName, operator, subordinateFieldName) => fieldIsValidUtil({
fieldName,
fieldsFlat,
operator,
subordinateFieldName,
getValueSources
});
function processMongoDbQueryBooleanOperator(field, mdbOperator, keyValue) {
let operator = "=";
let value = "";
if (mdbOperator === "$eq" || mdbOperator === "$ne" || mdbOperator === "$gt" || mdbOperator === "$gte" || mdbOperator === "$lt" || mdbOperator === "$lte") {
if (mdbOperator === "$ne" && keyValue === null) {
if (fieldIsValid(field, "notNull")) {
return { field, operator: "notNull", value: null };
}
} else {
operator = mongoDbToRqbOperatorMap[mdbOperator];
if (fieldIsValid(field, operator)) {
return { field, operator, value: keyValue };
}
}
} else if (mdbOperator === "$regex" && /^[^$^]$|^[^^].*[^$]$/.test(getRegExStr(keyValue))) {
if (fieldIsValid(field, "contains")) {
return {
field,
operator: "contains",
value: getRegExStr(keyValue)
};
}
} else if (mdbOperator === "$regex" && /^\^.*[^$]/.test(getRegExStr(keyValue))) {
if (fieldIsValid(field, "beginsWith")) {
return {
field,
operator: "beginsWith",
value: getRegExStr(keyValue).replace(/^\^/, "")
};
}
} else if (mdbOperator === "$regex" && /[^^].*\$/.test(getRegExStr(keyValue))) {
if (fieldIsValid(field, "endsWith")) {
return {
field,
operator: "endsWith",
value: getRegExStr(keyValue).replace(/\$$/, "")
};
}
} else if (mdbOperator === "$in" && Array.isArray(keyValue)) {
if (fieldIsValid(field, "in")) {
value = listsAsArrays ? keyValue : joinWith(
keyValue.map((v) => `${v}`),
","
);
return { field, operator: "in", value };
}
} else if (mdbOperator === "$nin" && Array.isArray(keyValue) && fieldIsValid(field, "notIn")) {
value = listsAsArrays ? keyValue : joinWith(
keyValue.map((v) => `${v}`),
","
);
return { field, operator: "notIn", value };
}
return false;
}
function processMongoDbQueryObjectKey(key, keyValue) {
let field = "";
if (key === "$and") {
if (!Array.isArray(keyValue) || keyValue.length === 0 || !keyValue.every((v) => isPojo(v))) {
return false;
}
if (keyValue.length === 2 && keyValue.every((kv) => objectKeys(kv).length === 1)) {
const [rule1, rule2] = keyValue;
const [ruleKey1, ruleKey2] = keyValue.map((kv) => objectKeys(kv)[0]);
if (ruleKey1 === ruleKey2 && isPojo(rule1[ruleKey1]) && objectKeys(rule1[ruleKey1]).length === 1 && isPojo(rule2[ruleKey2]) && objectKeys(rule2[ruleKey2]).length === 1 && ("$gte" in rule1[ruleKey1] && "$lte" in rule2[ruleKey2] && rule2[ruleKey2].$lte >= rule1[ruleKey1].$gte || "$lte" in rule1[ruleKey1] && "$gte" in rule2[ruleKey2] && rule1[ruleKey1].$lte >= rule2[ruleKey2].$gte)) {
const [val1, val2] = [
rule1[ruleKey1].$gte ?? rule1[ruleKey1].$lte,
rule2[ruleKey2].$lte ?? rule2[ruleKey2].$gte
];
let value = listsAsArrays ? [val1, val2] : joinWith([val1, val2], ",");
if (val1 > val2) {
value = listsAsArrays ? [val2, val1] : joinWith([val2, val1], ",");
}
return { field: ruleKey1, operator: "between", value };
}
}
const rules = keyValue.map((l) => processMongoDbQueryObject(l)).filter(Boolean);
return rules.length > 0 ? { combinator: "and", rules } : false;
} else if (key === "$or") {
if (!Array.isArray(keyValue) || keyValue.length === 0 || !keyValue.every((v) => isPojo(v))) {
return false;
}
if (keyValue.length === 2 && keyValue.every((kv) => objectKeys(kv).length === 1)) {
const [rule1, rule2] = keyValue;
const [ruleKey1, ruleKey2] = keyValue.map((kv) => objectKeys(kv)[0]);
if (ruleKey1 === ruleKey2 && isPojo(rule1[ruleKey1]) && objectKeys(rule1[ruleKey1]).length === 1 && isPojo(rule2[ruleKey2]) && objectKeys(rule2[ruleKey2]).length === 1 && ("$gt" in rule1[ruleKey1] && "$lt" in rule2[ruleKey2] && rule1[ruleKey1].$gt >= rule2[ruleKey2].$lt || "$lt" in rule1[ruleKey1] && "$gt" in rule2[ruleKey2] && rule2[ruleKey2].$gt >= rule1[ruleKey1].$lt)) {
const [val1, val2] = [
rule1[ruleKey1].$gt ?? rule1[ruleKey1].$lt,
rule2[ruleKey2].$lt ?? rule2[ruleKey2].$gt
];
let value = listsAsArrays ? [val1, val2] : `${val1},${val2}`;
if (val1 > val2) {
value = listsAsArrays ? [val2, val1] : `${val2},${val1}`;
}
return { field: ruleKey1, operator: "notBetween", value };
}
}
const rules = keyValue.map((l) => processMongoDbQueryObject(l)).filter(Boolean);
return rules.length > 0 ? { combinator: "or", rules } : false;
} else if (key === "$not" && isPojo(keyValue)) {
const ruleOrGroup = processMongoDbQueryObject(keyValue);
if (ruleOrGroup) {
if (isRuleGroupType(ruleOrGroup)) {
return ruleOrGroup.not ? { combinator: "and", rules: [ruleOrGroup], not: true } : { ...ruleOrGroup, not: true };
}
return preventOperatorNegation ? { combinator: "and", rules: [ruleOrGroup], not: true } : { ...ruleOrGroup, operator: defaultOperatorNegationMap[ruleOrGroup.operator] };
}
return false;
} else if (key === "$expr") {
const op = objectKeys(keyValue)[0];
if (/^\$(eq|gte?|lte?|n?in)$/.test(op) && Array.isArray(keyValue[op]) && keyValue[op].length === 2 && typeof keyValue[op][0] === "string" && keyValue[op][0].startsWith("$")) {
field = keyValue[op][0].replace(/^\$/, "");
const val = keyValue[op][1];
if (typeof val === "string" && val.startsWith("$") || Array.isArray(val) && val.every((v) => typeof v === "string") && val.every((v) => v.startsWith("$"))) {
const valForProcessing = Array.isArray(val) ? val.map((v) => v.replace(/^\$/, "")) : val.replace(/^\$/, "");
const tempRule = processMongoDbQueryBooleanOperator(field, op, valForProcessing);
if (tempRule) {
if (typeof tempRule.value === "string" && !fieldIsValid(field, tempRule.operator, tempRule.value)) {
return false;
}
return { ...tempRule, valueSource: "field" };
}
}
return processMongoDbQueryBooleanOperator(field, op, keyValue[op][1]);
}
} else if (/^[^$]/.test(key)) {
field = key;
if (isPrimitive(keyValue)) {
if (fieldIsValid(field, "=")) {
return { field, operator: "=", value: keyValue };
}
} else if (keyValue === null) {
if (fieldIsValid(field, "null")) {
return { field, operator: "null", value: keyValue };
}
} else if (isPojo(keyValue)) {
let betweenRule = false;
let notRule = false;
const additionalOpKeys = objectKeys(additionalOperators).map((o) => o.replace(/^\$/, ""));
const allOps = ["eq", "ne", "gte?", "lte?", "n?in", "regex", "not", ...additionalOpKeys];
const acceptedOpsRegExp = new RegExp(`^\\$(${allOps.join("|")})$`);
const operators = objectKeys(keyValue).filter((o) => acceptedOpsRegExp.test(o)).sort();
if (operators.length === 0) {
return false;
}
if ("$not" in keyValue && isPojo(keyValue.$not)) {
const invertedNotRule = processMongoDbQueryObject({ [field]: keyValue.$not });
if (invertedNotRule) {
if (isRuleGroupType(invertedNotRule)) {
notRule = { ...invertedNotRule, not: true };
} else {
notRule = preventOperatorNegation ? { combinator: "and", rules: [invertedNotRule], not: true } : {
...invertedNotRule,
operator: defaultOperatorNegationMap[invertedNotRule.operator]
};
}
}
}
if ("$gte" in keyValue && "$lte" in keyValue) {
betweenRule = {
field,
operator: "between",
value: listsAsArrays ? [keyValue.$gte, keyValue.$lte] : `${keyValue.$gte},${keyValue.$lte}`
};
}
const rules = operators.filter((op) => !(notRule && op === "$not")).filter((op) => !(betweenRule && (op === "$gte" || op === "$lte"))).map(
(op) => op in additionalOperators && typeof additionalOperators[op] === "function" ? additionalOperators[op](field, op, keyValue[op], otherOptions) : processMongoDbQueryBooleanOperator(field, op, keyValue[op])
).filter(Boolean);
if (notRule) {
rules.unshift(notRule);
}
if (betweenRule) {
rules.unshift(betweenRule);
}
if (rules.length === 0) {
return false;
}
if (rules.length === 1) {
return rules[0];
}
return { combinator: "and", rules };
}
}
return false;
}
function processMongoDbQueryObject(mongoDbQueryObject) {
const rules = objectKeys(mongoDbQueryObject).map((k) => processMongoDbQueryObjectKey(k, mongoDbQueryObject[k])).filter(Boolean);
return rules.length === 1 ? rules[0] : rules.length > 1 ? { combinator: "and", rules } : false;
}
const prepare = options.generateIDs ? prepareRuleGroup : (g) => g;
let mongoDbPOJO = mongoDbRules;
if (typeof mongoDbRules === "string") {
try {
mongoDbPOJO = JSON.parse(mongoDbRules);
} catch {
return prepare(emptyRuleGroup);
}
}
if (!isPojo(mongoDbPOJO)) {
return prepare(emptyRuleGroup);
}
const result = processMongoDbQueryObject(mongoDbPOJO);
const finalQuery = result ? isRuleGroupType(result) ? result : { combinator: "and", rules: [result] } : emptyRuleGroup;
return prepare(
options.independentCombinators ? convertToIC(finalQuery) : finalQuery
);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
parseMongoDB
});
//# sourceMappingURL=parseMongoDB.js.map