react-querybuilder
Version:
React Query Builder component for constructing queries and filters, with utilities for executing them in various database and evaluation contexts
1,426 lines (1,399 loc) • 324 kB
JavaScript
import * as React from "react";
import { Fragment, createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { produce } from "immer";
import { numericQuantity, numericRegex } from "numeric-quantity";
import { createDispatchHook, createSelectorHook, createStoreHook } from "react-redux";
import { createSlice } from "@reduxjs/toolkit";
//#region src/context/QueryBuilderContext.ts
/**
* Context provider for {@link QueryBuilder}. Any descendant query builders
* will inherit the props from a context provider.
*/
const QueryBuilderContext = createContext({});
//#endregion
//#region src/components/ActionElement.tsx
/**
* Default `<button>` component used by {@link QueryBuilder}.
*
* @group Components
*/
const ActionElement = (props) => /* @__PURE__ */ React.createElement("button", {
type: "button",
"data-testid": props.testID,
disabled: props.disabled && !props.disabledTranslation,
className: props.className,
title: props.disabledTranslation && props.disabled ? props.disabledTranslation.title : props.title,
onClick: (e) => props.handleOnClick(e)
}, props.disabledTranslation && props.disabled ? props.disabledTranslation.label : props.label);
//#endregion
//#region src/components/DragHandle.tsx
/**
* Default drag handle component used by {@link QueryBuilder} when `enableDragAndDrop` is `true`.
*
* @group Components
*/
const DragHandle = forwardRef((props, dragRef) => /* @__PURE__ */ React.createElement("span", {
"data-testid": props.testID,
ref: dragRef,
className: props.className,
title: props.title
}, props.label));
//#endregion
//#region src/defaults.ts
/**
* @group Defaults
*/
const defaultPlaceholderName = "~";
/**
* @group Defaults
*/
const defaultPlaceholderLabel = "------";
/**
* Default `name` for placeholder option in the `fields` array.
*
* @group Defaults
*/
const defaultPlaceholderFieldName = defaultPlaceholderName;
/**
* Default `label` for placeholder option in the `fields` array.
*
* @group Defaults
*/
const defaultPlaceholderFieldLabel = defaultPlaceholderLabel;
/**
* Default `label` for placeholder option group in the `fields` array.
*
* @group Defaults
*/
const defaultPlaceholderFieldGroupLabel = defaultPlaceholderLabel;
/**
* Default `name` for placeholder option in the `operators` array.
*
* @group Defaults
*/
const defaultPlaceholderOperatorName = defaultPlaceholderName;
/**
* Default `label` for placeholder option in the `operators` array.
*
* @group Defaults
*/
const defaultPlaceholderOperatorLabel = defaultPlaceholderLabel;
/**
* Default `label` for placeholder option group in the `operators` array.
*
* @group Defaults
*/
const defaultPlaceholderOperatorGroupLabel = defaultPlaceholderLabel;
/**
* Default `name` for placeholder option in the `values` array.
*
* @group Defaults
*/
const defaultPlaceholderValueName = defaultPlaceholderName;
/**
* Default `label` for placeholder option in the `values` array.
*
* @group Defaults
*/
const defaultPlaceholderValueLabel = defaultPlaceholderLabel;
/**
* Default `label` for placeholder option group in the `values` array.
*
* @group Defaults
*/
const defaultPlaceholderValueGroupLabel = defaultPlaceholderLabel;
/**
* Default character used to `.join` and `.split` arrays.
*
* @group Defaults
*/
const defaultJoinChar = ",";
const defaultOperatorLabelMap = {
"=": "=",
"!=": "!=",
"<": "<",
">": ">",
"<=": "<=",
">=": ">=",
contains: "contains",
beginsWith: "begins with",
endsWith: "ends with",
doesNotContain: "does not contain",
doesNotBeginWith: "does not begin with",
doesNotEndWith: "does not end with",
null: "is null",
notNull: "is not null",
in: "in",
notIn: "not in",
between: "between",
notBetween: "not between"
};
const defaultCombinatorLabelMap = {
and: "AND",
or: "OR",
xor: "XOR"
};
/**
* Default operator list.
*
* @group Defaults
*/
const defaultOperators = [
{
name: "=",
value: "=",
label: "="
},
{
name: "!=",
value: "!=",
label: "!="
},
{
name: "<",
value: "<",
label: "<"
},
{
name: ">",
value: ">",
label: ">"
},
{
name: "<=",
value: "<=",
label: "<="
},
{
name: ">=",
value: ">=",
label: ">="
},
{
name: "contains",
value: "contains",
label: "contains"
},
{
name: "beginsWith",
value: "beginsWith",
label: "begins with"
},
{
name: "endsWith",
value: "endsWith",
label: "ends with"
},
{
name: "doesNotContain",
value: "doesNotContain",
label: "does not contain"
},
{
name: "doesNotBeginWith",
value: "doesNotBeginWith",
label: "does not begin with"
},
{
name: "doesNotEndWith",
value: "doesNotEndWith",
label: "does not end with"
},
{
name: "null",
value: "null",
label: "is null"
},
{
name: "notNull",
value: "notNull",
label: "is not null"
},
{
name: "in",
value: "in",
label: "in"
},
{
name: "notIn",
value: "notIn",
label: "not in"
},
{
name: "between",
value: "between",
label: "between"
},
{
name: "notBetween",
value: "notBetween",
label: "not between"
}
];
/**
* Map of default operators to their respective opposite/negating operators.
*
* @group Defaults
*/
const 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"
};
/**
* Default combinator list.
*
* @group Defaults
*/
const defaultCombinators = [{
name: "and",
value: "and",
label: "AND"
}, {
name: "or",
value: "or",
label: "OR"
}];
/**
* Default combinator list, with `XOR` added.
*
* @group Defaults
*/
const defaultCombinatorsExtended = [...defaultCombinators, {
name: "xor",
value: "xor",
label: "XOR"
}];
/**
* Default match modes.
*
* @group Defaults
*/
const defaultMatchModes = [
{
name: "all",
value: "all",
label: "all"
},
{
name: "some",
value: "some",
label: "some"
},
{
name: "none",
value: "none",
label: "none"
},
{
name: "atLeast",
value: "atLeast",
label: "at least"
},
{
name: "atMost",
value: "atMost",
label: "at most"
},
{
name: "exactly",
value: "exactly",
label: "exactly"
}
];
/**
* Standard classnames applied to each component.
*
* @group Defaults
*/
const standardClassnames = {
queryBuilder: "queryBuilder",
ruleGroup: "ruleGroup",
header: "ruleGroup-header",
body: "ruleGroup-body",
combinators: "ruleGroup-combinators",
addRule: "ruleGroup-addRule",
addGroup: "ruleGroup-addGroup",
cloneRule: "rule-cloneRule",
cloneGroup: "ruleGroup-cloneGroup",
removeGroup: "ruleGroup-remove",
notToggle: "ruleGroup-notToggle",
rule: "rule",
fields: "rule-fields",
matchMode: "rule-matchMode",
matchThreshold: "rule-matchThreshold",
operators: "rule-operators",
value: "rule-value",
removeRule: "rule-remove",
betweenRules: "betweenRules",
valid: "queryBuilder-valid",
invalid: "queryBuilder-invalid",
shiftActions: "shiftActions",
dndDragging: "dndDragging",
dndOver: "dndOver",
dndCopy: "dndCopy",
dndGroup: "dndGroup",
dragHandle: "queryBuilder-dragHandle",
disabled: "queryBuilder-disabled",
lockRule: "rule-lock",
lockGroup: "ruleGroup-lock",
valueSource: "rule-valueSource",
valueListItem: "rule-value-list-item",
branches: "queryBuilder-branches",
justified: "queryBuilder-justified",
hasSubQuery: "rule-hasSubQuery"
};
/**
* Default classnames for each component.
*
* @group Defaults
*/
const defaultControlClassnames = {
queryBuilder: "",
ruleGroup: "",
header: "",
body: "",
combinators: "",
addRule: "",
addGroup: "",
cloneRule: "",
cloneGroup: "",
removeGroup: "",
notToggle: "",
rule: "",
fields: "",
matchMode: "",
matchThreshold: "",
operators: "",
value: "",
removeRule: "",
shiftActions: "",
dragHandle: "",
lockRule: "",
lockGroup: "",
valueSource: "",
actionElement: "",
valueSelector: "",
betweenRules: "",
valid: "",
invalid: "",
dndDragging: "",
dndOver: "",
dndGroup: "",
dndCopy: "",
disabled: "",
valueListItem: "",
branches: "",
hasSubQuery: ""
};
/**
* Default reason codes for a group being invalid.
*
* @group Defaults
*/
const groupInvalidReasons = {
empty: "empty",
invalidCombinator: "invalid combinator",
invalidIndependentCombinators: "invalid independent combinators"
};
/**
* Component identifiers for testing.
*
* @group Defaults
*/
const TestID = {
rule: "rule",
ruleGroup: "rule-group",
inlineCombinator: "inline-combinator",
addGroup: "add-group",
removeGroup: "remove-group",
cloneGroup: "clone-group",
cloneRule: "clone-rule",
addRule: "add-rule",
removeRule: "remove-rule",
combinators: "combinators",
fields: "fields",
operators: "operators",
valueEditor: "value-editor",
notToggle: "not-toggle",
shiftActions: "shift-actions",
dragHandle: "drag-handle",
lockRule: "lock-rule",
lockGroup: "lock-group",
valueSourceSelector: "value-source-selector",
matchModeEditor: "match-mode-editor"
};
const LogType = {
parentPathDisabled: "action aborted: parent path disabled",
pathDisabled: "action aborted: path is disabled",
queryUpdate: "query updated",
onAddRuleFalse: "onAddRule callback returned false",
onAddGroupFalse: "onAddGroup callback returned false",
onGroupRuleFalse: "onGroupRule callback returned false",
onGroupGroupFalse: "onGroupGroup callback returned false",
onMoveRuleFalse: "onMoveRule callback returned false",
onMoveGroupFalse: "onMoveGroup callback returned false",
onRemoveFalse: "onRemove callback returned false",
add: "rule or group added",
remove: "rule or group removed",
update: "rule or group updated",
move: "rule or group moved",
group: "rule or group grouped with another"
};
/**
* The {@link Path} of the root group.
*
* @group Defaults
*/
const rootPath = [];
//#endregion
//#region src/utils/clsx.ts
// istanbul ignore next
function toVal(mix) {
let k;
let y;
let str = "";
if (typeof mix === "string" || typeof mix === "number") str += mix;
else if (typeof mix === "object") {
if (Array.isArray(mix)) {
const len = mix.length;
for (k = 0; k < len; k++) if (mix[k] && (y = toVal(mix[k]))) {
str && (str += " ");
str += y;
}
} else for (y in mix) if (mix[y]) {
str && (str += " ");
str += y;
}
}
return str;
}
// istanbul ignore next
function clsx(...args) {
let i = 0;
let tmp;
let x;
let str = "";
const len = args.length;
for (; i < len; i++) if ((tmp = args[i]) && (x = toVal(tmp))) {
str && (str += " ");
str += x;
}
return str;
}
var clsx_default = clsx;
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/objectWithoutPropertiesLoose.js
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (e.includes(n)) continue;
t[n] = r[n];
}
return t;
}
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/objectWithoutProperties.js
function _objectWithoutProperties(e, t) {
if (null == e) return {};
var o, r, i = _objectWithoutPropertiesLoose(e, t);
if (Object.getOwnPropertySymbols) {
var s = Object.getOwnPropertySymbols(e);
for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
}
return i;
}
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/typeof.js
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
return typeof o$1;
} : function(o$1) {
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
}, _typeof(o);
}
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/toPrimitive.js
function toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/toPropertyKey.js
function toPropertyKey(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/defineProperty.js
function _defineProperty(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
//#endregion
//#region ../../node_modules/tsdown/node_modules/rolldown/node_modules/@oxc-project/runtime/src/helpers/esm/objectSpread2.js
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function(r$1) {
return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
_defineProperty(e, r$1, t[r$1]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
});
}
return e;
}
//#endregion
//#region src/components/InlineCombinator.tsx
const _excluded$3 = ["component"];
/**
* Default `inlineCombinator` component used by {@link QueryBuilder}. A small `<div>`
* wrapper around the `combinatorSelector` component, used when either
* `showCombinatorsBetweenRules` or `independentCombinators` are `true`.
*
* @group Components
*/
const InlineCombinator = (allProps) => {
const { component: CombinatorSelectorComponent } = allProps, props = _objectWithoutProperties(allProps, _excluded$3);
const className = clsx(props.schema.suppressStandardClassnames || standardClassnames.betweenRules, props.schema.classNames.betweenRules);
return /* @__PURE__ */ React.createElement("div", {
className,
"data-testid": TestID.inlineCombinator
}, /* @__PURE__ */ React.createElement(CombinatorSelectorComponent, _objectSpread2(_objectSpread2({}, props), {}, { testID: TestID.combinators })));
};
//#endregion
//#region src/utils/arrayUtils.ts
/**
* Splits a string by a given character (see {@link defaultJoinChar}). Escaped characters
* (characters preceded by a backslash) will not apply to the split, and the backslash will
* be removed in the array element. Inverse of {@link joinWith}.
*
* @example
* splitBy('this\\,\\,that,,the other,,,\\,')
* // or
* splitBy('this\\,\\,that,,the other,,,\\,', ',')
* // would return
* ['this,,that', '', 'the other', '', '', ',']
*/
const splitBy = (str, splitChar = defaultJoinChar) => typeof str === "string" ? str.split(`\\${splitChar}`).map((c) => c.split(splitChar)).reduce((prev, curr, idx) => {
if (idx === 0) return curr;
return [
...prev.slice(0, -1),
`${prev.at(-1)}${splitChar}${curr[0]}`,
...curr.slice(1)
];
}, []) : [];
/**
* Joins an array of strings using the given character (see {@link defaultJoinChar}). When
* the given character appears in an array element, a backslash will be added just before it
* to distinguish it from the join character. Effectively the inverse of {@link splitBy}.
*
* TIP: The join character can actually be a string of any length. Only the first character
* will be searched for in the array elements and preceded by a backslash.
*
* @example
* joinWith(['this,,that', '', 'the other', '', '', ','], ', ')
* // would return
* 'this\\,\\,that, , the other, , , \\,'
*/
const joinWith = (strArr, joinChar = defaultJoinChar) => strArr.map((str) => `${str !== null && str !== void 0 ? str : ""}`.replaceAll(joinChar[0], `\\${joinChar[0]}`)).join(joinChar);
/**
* Trims the value if it is a string. Otherwise returns the value as is.
*/
const trimIfString = (val) => typeof val === "string" ? val.trim() : val;
/**
* Splits a string by comma then trims each element. Arrays are returned as is except
* any string elements are trimmed.
*/
const toArray = (v, { retainEmptyStrings } = {}) => Array.isArray(v) ? v.map((v$1) => trimIfString(v$1)) : typeof v === "string" ? splitBy(v, defaultJoinChar).filter(retainEmptyStrings ? () => true : (s) => !/^\s*$/.test(s)).map((s) => s.trim()) : typeof v === "number" ? [v] : [];
/**
* Determines if an array is free of `null`/`undefined`.
*/
const nullFreeArray = (arr) => arr.every((el) => el === false || (el !== null && el !== void 0 ? el : false) !== false);
//#endregion
//#region src/utils/misc.ts
/**
* Converts a value to lowercase if it's a string, otherwise returns the value as is.
*/
// istanbul ignore next
const lc = (v) => typeof v === "string" ? v.toLowerCase() : v;
/**
* Regex matching numeric strings. Passes for positive/negative integers, decimals,
* and E notation, with optional surrounding whitespace.
*/
const numericRegex$1 = new RegExp(numericRegex.source.replace(/^\^/, String.raw`^\s*`).replace(/\$$/, String.raw`\s*$`));
/**
* Determines if a variable is a plain old JavaScript object, aka POJO.
*/
const isPojo = (obj) => obj === null || typeof obj !== "object" ? false : Object.getPrototypeOf(obj) === Object.prototype;
/**
* Simple helper to determine whether a value is null, undefined, or an empty string.
*/
const nullOrUndefinedOrEmpty = (value) => value === null || value === void 0 || value === "";
//#endregion
//#region src/utils/isRuleGroup.ts
/**
* Determines if an object is a {@link RuleGroupType} or {@link RuleGroupTypeIC}.
*/
const isRuleGroup = (rg) => isPojo(rg) && Array.isArray(rg.rules);
/**
* Determines if an object is a {@link RuleGroupType}.
*/
const isRuleGroupType = (rg) => isRuleGroup(rg) && typeof rg.combinator === "string";
/**
* Determines if an object is a {@link RuleGroupTypeIC}.
*/
const isRuleGroupTypeIC = (rg) => isRuleGroup(rg) && rg.combinator === void 0;
//#endregion
//#region src/utils/convertQuery.ts
const _excluded$2 = ["combinator"];
const combinatorLevels = [
"or",
"xor",
"and"
];
const isSameString = (a, b) => lc(a) === b;
const generateRuleGroupICWithConsistentCombinators = (rg, baseCombinatorLevel = 0) => {
const baseCombinator = combinatorLevels[baseCombinatorLevel];
if (!rg.rules.includes(baseCombinator)) return baseCombinatorLevel < combinatorLevels.length - 2 ? generateRuleGroupICWithConsistentCombinators(rg, baseCombinatorLevel + 1) : rg;
return produce(rg, (draft) => {
let cursor = 0;
while (cursor < draft.rules.length - 2) {
if (isSameString(draft.rules[cursor + 1], baseCombinator)) {
cursor += 2;
continue;
}
const nextBaseCombinatorIndex = draft.rules.findIndex((r, i) => i > cursor && typeof r === "string" && lc(r) === baseCombinator);
if (nextBaseCombinatorIndex === -1) {
draft.rules.splice(cursor, draft.rules.length, generateRuleGroupICWithConsistentCombinators({ rules: draft.rules.slice(cursor) }, baseCombinatorLevel + 1));
break;
} else draft.rules.splice(cursor, nextBaseCombinatorIndex - cursor, generateRuleGroupICWithConsistentCombinators({ rules: draft.rules.slice(cursor, nextBaseCombinatorIndex) }, baseCombinatorLevel + 1));
}
});
};
/**
* Converts a {@link RuleGroupTypeIC} to {@link RuleGroupType}.
*
* This function is idempotent: {@link RuleGroupType} queries will be
* returned as-is.
*
* @group Query Tools
*/
const convertFromIC = (rg) => {
if (isRuleGroupType(rg)) return rg;
const processedRG = generateRuleGroupICWithConsistentCombinators(rg);
const rulesAsMixedList = processedRG.rules.map((r) => typeof r === "string" || !isRuleGroup(r) ? r : convertFromIC(r));
const combinator = rulesAsMixedList.length < 2 ? "and" : rulesAsMixedList[1];
const rules = rulesAsMixedList.filter((r) => typeof r !== "string");
return _objectSpread2(_objectSpread2({}, processedRG), {}, {
combinator,
rules
});
};
/**
* Converts a {@link RuleGroupType} to {@link RuleGroupTypeIC}.
*
* This function is idempotent: {@link RuleGroupTypeIC} queries will be
* returned as-is.
*
* @group Query Tools
*/
const convertToIC = (rg) => {
if (isRuleGroupTypeIC(rg)) return rg;
const { combinator } = rg, queryWithoutCombinator = _objectWithoutProperties(rg, _excluded$2);
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 _objectSpread2(_objectSpread2({}, queryWithoutCombinator), {}, { rules });
};
function convertQuery(query) {
return isRuleGroupTypeIC(query) ? convertFromIC(query) : convertToIC(query);
}
//#endregion
//#region src/utils/defaultValidator.ts
/**
* This is an example validation function you can pass to {@link QueryBuilder} in the
* `validator` prop. It assumes that you want to validate groups, and has a no-op
* for validating rules which you can replace with your own implementation.
*/
const defaultValidator = (query) => {
const result = {};
const validateRule = (_rule) => {};
const validateGroup = (rg) => {
const reasons = [];
if (rg.rules.length === 0) reasons.push(groupInvalidReasons.empty);
else if (!isRuleGroupType(rg)) {
let invalidICs = false;
for (let i = 0; i < rg.rules.length && !invalidICs; i++) if (i % 2 === 0 && typeof rg.rules[i] === "string" || i % 2 === 1 && typeof rg.rules[i] !== "string" || i % 2 === 1 && typeof rg.rules[i] === "string" && !defaultCombinators.map((c) => c.name).includes(rg.rules[i])) invalidICs = true;
if (invalidICs) reasons.push(groupInvalidReasons.invalidIndependentCombinators);
}
if (isRuleGroupType(rg) && !defaultCombinators.map((c) => c.name).includes(rg.combinator) && rg.rules.length > 1) reasons.push(groupInvalidReasons.invalidCombinator);
/* istanbul ignore else */
if (rg.id) result[rg.id] = reasons.length > 0 ? {
valid: false,
reasons
} : true;
for (const r of rg.rules) if (typeof r === "string") {} else if (isRuleGroup(r)) validateGroup(r);
else validateRule(r);
};
validateGroup(query);
return result;
};
//#endregion
//#region src/utils/optGroupUtils.ts
const isOptionWithName = (opt) => isPojo(opt) && "name" in opt && typeof opt.name === "string";
const isOptionWithValue = (opt) => isPojo(opt) && "value" in opt && typeof opt.value === "string";
/**
* Converts an {@link Option} or {@link ValueOption} (i.e., {@link BaseOption})
* into a {@link FullOption}. Full options are left unchanged.
*
* @group Option Lists
*/
function toFullOption(opt, baseProperties, labelMap) {
const recipe = produce((draft) => {
const idObj = {};
let needsUpdating = !!baseProperties;
if (typeof draft === "string") {
var _labelMap$draft;
return _objectSpread2(_objectSpread2({}, baseProperties), {}, {
name: draft,
value: draft,
label: (_labelMap$draft = labelMap === null || labelMap === void 0 ? void 0 : labelMap[draft]) !== null && _labelMap$draft !== void 0 ? _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);
}
/**
* Converts an {@link OptionList} or {@link FlexibleOptionList} into a {@link FullOptionList}.
* Lists of full options are left unchanged.
*
* @group Option Lists
*/
function toFullOptionList(optList, baseProperties, labelMap) {
if (!Array.isArray(optList)) return [];
const recipe = 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);
}
/**
* Converts a {@link FlexibleOptionList} into a {@link FullOptionList}.
* Lists of full options are left unchanged.
*
* @group Option Lists
*/
function toFullOptionMap(optMap, baseProperties) {
return Object.fromEntries(Object.entries(optMap).map(([k, v]) => [k, toFullOption(v, baseProperties)]));
}
/**
* @deprecated Renamed to {@link uniqByIdentifier}.
*
* @group Option Lists
*/
const uniqByName = (originalArray) => uniqByIdentifier(originalArray);
/**
* Generates a new array of objects with duplicates removed based
* on the identifying property (`value` or `name`)
*
* @group Option Lists
*/
const uniqByIdentifier = (originalArray) => {
const names = /* @__PURE__ */ new Set();
const newArray = [];
for (const el of originalArray) {
var _el$value;
if (!names.has((_el$value = el.value) !== null && _el$value !== void 0 ? _el$value : el.name)) {
var _el$value2;
names.add((_el$value2 = el.value) !== null && _el$value2 !== void 0 ? _el$value2 : el.name);
newArray.push(el);
}
}
return originalArray.length === newArray.length ? originalArray : newArray;
};
/**
* Determines if an {@link OptionList} is an {@link OptionGroup} array.
*
* @group Option Lists
*/
const isOptionGroupArray = (arr) => Array.isArray(arr) && arr.length > 0 && isPojo(arr[0]) && "options" in arr[0] && Array.isArray(arr[0].options);
/**
* Determines if an array is a flat array of {@link FlexibleOption}.
*
* @group Option Lists
*/
const 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;
};
/**
* Determines if an array is a flat array of {@link FullOption}.
*
* @group Option Lists
*/
const isFullOptionArray = (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;
};
/**
* Determines if a {@link FlexibleOptionList} is a {@link FlexibleOptionGroup} array.
*
* @group Option Lists
*/
const 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;
};
/**
* Determines if a {@link FlexibleOptionList} is a {@link OptionGroup} array of {@link FullOption}.
*
* @group Option Lists
*/
const isFullOptionGroupArray = (arr, { allowEmpty = false } = {}) => {
let isFOGA = false;
if (Array.isArray(arr)) for (const og of arr) if (isPojo(og) && "options" in og && (isFullOptionArray(og.options) || allowEmpty && Array.isArray(og.options) && og.options.length === 0)) isFOGA = true;
else return false;
return isFOGA;
};
function getOption(arr, name) {
const options = isFlexibleOptionGroupArray(arr, { allowEmpty: true }) ? arr.flatMap((og) => og.options) : arr;
return options.find((op) => op.value === name || op.name === name);
}
function getFirstOption(arr) {
var _arr$0$value;
if (!Array.isArray(arr) || arr.length === 0) return null;
else if (isFlexibleOptionGroupArray(arr, { allowEmpty: true })) {
for (const og of arr) if (og.options.length > 0) {
var _og$options$0$value;
return (_og$options$0$value = og.options[0].value) !== null && _og$options$0$value !== void 0 ? _og$options$0$value : og.options[0].name;
}
// istanbul ignore next
return null;
}
return (_arr$0$value = arr[0].value) !== null && _arr$0$value !== void 0 ? _arr$0$value : arr[0].name;
}
/**
* Flattens {@link FlexibleOptionGroup} arrays into {@link BaseOption} arrays.
* If the array is already flat, it is returned as is.
*
* @group Option Lists
*/
const toFlatOptionArray = (arr) => uniqByIdentifier(isOptionGroupArray(arr) ? arr.flatMap((og) => og.options) : arr);
/**
* Generates a new {@link OptionGroup} array with duplicates
* removed based on the identifying property (`value` or `name`).
*
* @group Option Lists
*/
const uniqOptGroups = (originalArray) => {
const labels = /* @__PURE__ */ new Set();
const names = /* @__PURE__ */ new Set();
const newArray = [];
for (const el of originalArray) if (!labels.has(el.label)) {
labels.add(el.label);
const optionsForThisGroup = [];
for (const opt of el.options) {
var _opt$value;
if (!names.has((_opt$value = opt.value) !== null && _opt$value !== void 0 ? _opt$value : opt.name)) {
var _opt$value2;
names.add((_opt$value2 = opt.value) !== null && _opt$value2 !== void 0 ? _opt$value2 : opt.name);
optionsForThisGroup.push(toFullOption(opt));
}
}
newArray.push(_objectSpread2(_objectSpread2({}, el), {}, { options: optionsForThisGroup }));
}
return newArray;
};
/**
* Generates a new {@link Option} or {@link OptionGroup} array with duplicates
* removed based on the identifier property (`value` or `name`).
*
* @group Option Lists
*/
const uniqOptList = (originalArray) => {
if (isFlexibleOptionGroupArray(originalArray)) return uniqOptGroups(originalArray);
return uniqByIdentifier(originalArray.map((o) => toFullOption(o)));
};
//#endregion
//#region src/utils/filterFieldsByComparator.ts
const filterByComparator = (field, operator, fieldToCompare) => {
var _fullField$comparator, _fullField$comparator2;
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 = (_fullField$comparator2 = fullField.comparator) === null || _fullField$comparator2 === void 0 ? void 0 : _fullField$comparator2.call(fullField, fullFieldToCompare, operator)) !== null && _fullField$comparator !== void 0 ? _fullField$comparator : false;
};
/**
* For a given {@link FullField}, returns the `fields` list filtered for
* other fields that match by `comparator`. Only fields *other than the
* one in question* will ever be included, even if `comparator` is `null`
* or `undefined`. If `comparator` is a string, fields with the same value
* for that property will be included. If `comparator` is a function, each
* field will be passed to the function along with the `operator` and fields
* for which the function returns `true` will be included.
*
* @group Option Lists
*/
const filterFieldsByComparator = (field, fields, operator) => {
if (!field.comparator) {
const filterOutSameField = (f) => {
var _f$value, _field$value;
return ((_f$value = f.value) !== null && _f$value !== void 0 ? _f$value : f.name) !== ((_field$value = field.value) !== null && _field$value !== void 0 ? _field$value : field.name);
};
if (isFlexibleOptionGroupArray(fields)) return fields.map((og) => _objectSpread2(_objectSpread2({}, og), {}, { options: og.options.filter((v) => filterOutSameField(v)) }));
return fields.filter((v) => filterOutSameField(v));
}
if (isFlexibleOptionGroupArray(fields)) return fields.map((og) => _objectSpread2(_objectSpread2({}, og), {}, { options: og.options.filter((f) => filterByComparator(field, operator, f)) })).filter((og) => og.options.length > 0);
return fields.filter((f) => filterByComparator(field, operator, f));
};
//#endregion
//#region src/utils/parseNumber.ts
/**
* Converts a string to a number. Uses native `parseFloat` if `parseNumbers` is "native",
* otherwise uses [`numeric-quantity`](https://jakeboone02.github.io/numeric-quantity/).
* If that returns `NaN`, the string is returned unchanged. Numeric values are returned
* as-is regardless of the `parseNumbers` option.
*/
const parseNumber = (val, { parseNumbers, bigIntOnOverflow } = {}) => {
if (!parseNumbers || typeof val === "bigint" || typeof val === "number") return val;
if (parseNumbers === "native") return Number.parseFloat(val);
const valAsNum = numericQuantity(val, {
allowTrailingInvalid: parseNumbers === "enhanced",
bigIntOnOverflow,
romanNumerals: false,
round: false
});
return typeof valAsNum === "bigint" || !Number.isNaN(valAsNum) ? valAsNum : val;
};
//#endregion
//#region src/utils/transformQuery.ts
const remapProperties = (obj, propertyMap, deleteRemappedProperties) => produce(obj, (draft) => {
for (const [k, v] of Object.entries(propertyMap)) if (v === false) delete draft[k];
else if (!!v && k !== v && k in draft) {
draft[v] = draft[k];
if (deleteRemappedProperties) delete draft[k];
}
});
function transformQuery(query, options = {}) {
const { ruleProcessor = (r) => r, ruleGroupProcessor = (rg) => rg, propertyMap = {}, combinatorMap = {}, operatorMap = {}, omitPath = false, deleteRemappedProperties = true } = options;
const processGroup = (rg) => {
var _combinatorMap$rg$com, _propertyMap$rules;
return _objectSpread2(_objectSpread2({}, ruleGroupProcessor(remapProperties(_objectSpread2(_objectSpread2({}, rg), isRuleGroupType(rg) ? { combinator: (_combinatorMap$rg$com = combinatorMap[rg.combinator]) !== null && _combinatorMap$rg$com !== void 0 ? _combinatorMap$rg$com : rg.combinator } : {}), propertyMap, deleteRemappedProperties))), propertyMap["rules"] === false ? null : { [(_propertyMap$rules = propertyMap["rules"]) !== null && _propertyMap$rules !== void 0 ? _propertyMap$rules : "rules"]: rg.rules.map((r, idx) => {
var _operatorMap$r$operat;
const pathObject = omitPath ? null : { path: [...rg.path, idx] };
if (typeof r === "string") {
var _combinatorMap$r;
return (_combinatorMap$r = combinatorMap[r]) !== null && _combinatorMap$r !== void 0 ? _combinatorMap$r : r;
} else if (isRuleGroup(r)) return processGroup(_objectSpread2(_objectSpread2({}, r), pathObject));
return ruleProcessor(remapProperties(_objectSpread2(_objectSpread2(_objectSpread2({}, r), pathObject), "operator" in r ? { operator: (_operatorMap$r$operat = operatorMap[r.operator]) !== null && _operatorMap$r$operat !== void 0 ? _operatorMap$r$operat : r.operator } : {}), propertyMap, deleteRemappedProperties));
}) });
};
return processGroup(_objectSpread2(_objectSpread2({}, query), omitPath ? null : { path: [] }));
}
//#endregion
//#region src/utils/isRuleOrGroupValid.ts
/**
* Determines if an object is useful as a validation result.
*/
const isValidationResult = (vr) => isPojo(vr) && typeof vr.valid === "boolean";
/**
* Determines if a rule or group is valid based on a validation result (if defined)
* or a validator function. Returns `true` if neither are defined.
*/
const isRuleOrGroupValid = (rg, validationResult, validator) => {
if (typeof validationResult === "boolean") return validationResult;
if (isValidationResult(validationResult)) return validationResult.valid;
if (typeof validator === "function" && !isRuleGroup(rg)) {
const vr = validator(rg);
if (typeof vr === "boolean") return vr;
// istanbul ignore else
if (isValidationResult(vr)) return vr.valid;
}
return true;
};
//#endregion
//#region src/utils/getParseNumberMethod.ts
const getParseNumberMethod = ({ parseNumbers, inputType }) => {
if (typeof parseNumbers === "string") {
const [method, level] = parseNumbers.split("-");
if (level === "limited") return inputType === "number" ? method : false;
return method;
}
return parseNumbers ? "strict" : false;
};
//#endregion
//#region src/utils/formatQuery/utils.ts
/**
* Maps a {@link DefaultOperatorName} to a SQL operator.
*
* @group Export
*/
const mapSQLOperator = (rqbOperator) => {
switch (lc(rqbOperator)) {
case "null": return "is null";
case "notnull": return "is not null";
case "notin": return "not in";
case "notbetween": return "not between";
case "contains":
case "beginswith":
case "endswith": return "like";
case "doesnotcontain":
case "doesnotbeginwith":
case "doesnotendwith": return "not like";
default: return rqbOperator;
}
};
/**
* Maps a (lowercase) {@link DefaultOperatorName} to a MongoDB operator.
*
* @group Export
*/
const mongoOperators = {
"=": "$eq",
"!=": "$ne",
"<": "$lt",
"<=": "$lte",
">": "$gt",
">=": "$gte",
in: "$in",
notin: "$nin",
notIn: "$nin"
};
/**
* Maps a (lowercase) {@link DefaultOperatorName} to a Prisma ORM operator.
*
* @group Export
*/
const prismaOperators = {
"=": "equals",
"!=": "not",
"<": "lt",
"<=": "lte",
">": "gt",
">=": "gte",
in: "in",
notin: "notIn"
};
/**
* Maps a {@link DefaultCombinatorName} to a CEL combinator.
*
* @group Export
*/
const celCombinatorMap = {
and: "&&",
or: "||"
};
/**
* Register these operators with `jsonLogic` before applying the result
* of `formatQuery(query, 'jsonlogic')`.
*
* @example
* ```
* for (const [op, func] of Object.entries(jsonLogicAdditionalOperators)) {
* jsonLogic.add_operation(op, func);
* }
* jsonLogic.apply({ "startsWith": [{ "var": "firstName" }, "Stev"] }, data);
* ```
*
* @group Export
*/
const jsonLogicAdditionalOperators = {
startsWith: (a, b) => typeof a === "string" && a.startsWith(b),
endsWith: (a, b) => typeof a === "string" && a.endsWith(b)
};
/**
* Converts all `string`-type `value` properties of a query object into `number` where appropriate.
*
* Used by {@link formatQuery} for the `json*` formats when `parseNumbers` is `true`.
*
* @group Export
*/
const numerifyValues = (rg, options) => _objectSpread2(_objectSpread2({}, rg), {}, { rules: rg.rules.map((r) => {
if (typeof r === "string") return r;
if (isRuleGroup(r)) return numerifyValues(r, options);
const fieldData = getOption(options.fields, r.field);
const parseNumbers = getParseNumberMethod({
parseNumbers: options.parseNumbers,
inputType: fieldData === null || fieldData === void 0 ? void 0 : fieldData.inputType
});
if (Array.isArray(r.value)) return _objectSpread2(_objectSpread2({}, r), {}, { value: r.value.map((v) => parseNumber(v, { parseNumbers })) });
const valAsArray = toArray(r.value, { retainEmptyStrings: true }).map((v) => parseNumber(v, { parseNumbers }));
if (valAsArray.every((v) => typeof v === "number")) {
// istanbul ignore else
if (valAsArray.length > 1) return _objectSpread2(_objectSpread2({}, r), {}, { value: valAsArray });
else if (valAsArray.length === 1) return _objectSpread2(_objectSpread2({}, r), {}, { value: valAsArray[0] });
}
return r;
}) });
/**
* Determines whether a value is _anything_ except an empty `string` or `NaN`.
*
* @group Export
*/
const isValidValue = (value) => typeof value === "string" && value.length > 0 || typeof value === "number" && !Number.isNaN(value) || typeof value !== "string" && typeof value !== "number";
/**
* Determines whether {@link formatQuery} should render the given value as a number.
* As long as `parseNumbers` is `true`, `number` and `bigint` values will return `true` and
* `string` values will return `true` if they test positive against {@link numericRegex}.
*
* @group Export
*/
const shouldRenderAsNumber = (value, parseNumbers) => !!parseNumbers && (typeof value === "number" || typeof value === "bigint" || typeof value === "string" && numericRegex$1.test(value));
/**
* Used by {@link formatQuery} to determine whether the given value processor is a
* "legacy" value processor by counting the number of arguments. Legacy value
* processors take 3 arguments (not counting any arguments with default values), while
* rule-based value processors take no more than 2 arguments.
*
* @group Export
*/
const isValueProcessorLegacy = (valueProcessor) => valueProcessor.length >= 3;
/**
* Converts the `quoteFieldNamesWith` option into an array of two strings.
* If the option is a string, the array elements are both that string.
*
* @default
* ['', '']
*
* @group Export
*/
const getQuoteFieldNamesWithArray = (quoteFieldNamesWith = ["", ""]) => Array.isArray(quoteFieldNamesWith) ? quoteFieldNamesWith : typeof quoteFieldNamesWith === "string" ? [quoteFieldNamesWith, quoteFieldNamesWith] : quoteFieldNamesWith !== null && quoteFieldNamesWith !== void 0 ? quoteFieldNamesWith : ["", ""];
/**
* Given a field name and relevant {@link ValueProcessorOptions}, returns the field name
* wrapped in the configured quote character(s).
*
* @group Export
*/
const getQuotedFieldName = (fieldName, { quoteFieldNamesWith, fieldIdentifierSeparator }) => {
const [qPre, qPost] = getQuoteFieldNamesWithArray(quoteFieldNamesWith);
return typeof fieldIdentifierSeparator === "string" && fieldIdentifierSeparator.length > 0 ? joinWith(splitBy(fieldName, fieldIdentifierSeparator).map((part) => `${qPre}${part}${qPost}`), fieldIdentifierSeparator) : `${qPre}${fieldName}${qPost}`;
};
const defaultWordOrder = [
"S",
"V",
"O"
];
/**
* Given a [Constituent word order](https://en.wikipedia.org/wiki/Word_order#Constituent_word_orders)
* like "svo" or "sov", returns a permutation of `["S", "V", "O"]` based on the first occurrence of
* each letter in the input string (case insensitive). This widens the valid input from abbreviations
* like "svo" to more expressive strings like "subject-verb-object" or "sub ver obj". Any missing
* letters are appended in the default order "SVO" (e.g., "object" would yield `["O", "S", "V"]`).
*
* @group Export
*/
const normalizeConstituentWordOrder = (input) => {
const result = [];
const letterSet = new Set(defaultWordOrder);
for (const char of input.toUpperCase()) if (letterSet.has(char)) {
result.push(char);
letterSet.delete(char);
if (letterSet.size === 0) break;
}
for (const letter of defaultWordOrder) if (letterSet.has(letter)) result.push(letter);
return result;
};
/**
* Default translations used by {@link formatQuery} for "natural_language" format.
*
* @group Export
*/
const defaultNLTranslations = {
groupPrefix: "",
groupPrefix_not_xor: "either zero or more than one of",
groupPrefix_xor: "exactly one of",
groupSuffix: "is true",
groupSuffix_not: "is not true"
};
/**
* Note: This function assumes `conditions.length > 0`
*/
const translationMatchFilter = (key, keyToTest, conditions) => keyToTest.startsWith(key) && conditions.every((c) => {
var _keyToTest$match;
return keyToTest.includes(`_${c}`) && ((_keyToTest$match = keyToTest.match(/_/g)) === null || _keyToTest$match === void 0 ? void 0 : _keyToTest$match.length) === conditions.length;
});
/**
* Used by {@link formatQuery} to get a translation based on certain conditions
* for the "natural_language" format.
*
* @group Export
*/
const getNLTranslataion = (key, translations, conditions = []) => {
var _ref, _translations$key, _ref2, _ref3, _Object$entries$find$, _Object$entries$find, _Object$entries$find2;
return conditions.length === 0 ? (_ref = (_translations$key = translations[key]) !== null && _translations$key !== void 0 ? _translations$key : defaultNLTranslations[key]) !== null && _ref !== void 0 ? _ref : "" : (_ref2 = (_ref3 = (_Object$entries$find$ = (_Object$entries$find = Object.entries(translations).find(([keyToTest]) => translationMatchFilter(key, keyToTest, conditions))) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[1]) !== null && _Object$entries$find$ !== void 0 ? _Object$entries$find$ : (_Object$entries$find2 = Object.entries(defaultNLTranslations).find(([keyToTest]) => translationMatchFilter(key, keyToTest, conditions))) === null || _Object$entries$find2 === void 0 ? void 0 : _Object$entries$find2[1]) !== null && _ref3 !== void 0 ? _ref3 : defaultNLTranslations[key]) !== null && _ref2 !== void 0 ? _ref2 : "";
};
const processMatchMode = (rule) => {
var _rule$match;
const { mode, threshold } = (_rule$match = rule.match) !== null && _rule$match !== void 0 ? _rule$match : {};
if (mode) {
if (!isRuleGroup(rule.value)) return false;
const matchModeLC = lc(mode);
const matchModeCoerced = matchModeLC === "atleast" && threshold === 1 ? "some" : matchModeLC === "atmost" && threshold === 0 ? "none" : matchModeLC;
if ((matchModeCoerced === "atleast" || matchModeCoerced === "atmost" || matchModeCoerced === "exactly") && (typeof threshold !== "number" || threshold < 0)) return false;
return {
mode: matchModeCoerced,
threshold
};
}
};
/**
* "Replacer" method for JSON.stringify's second argument. Converts `bigint` values to
* objects with a `$bigint` property having a value of a string representation of
* the actual `bigint`-type value.
*
* Inverse of {@link bigIntJsonParseReviver}.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json
*/
const bigIntJsonStringifyReplacer = (_key, value) => typeof value === "bigint" ? { $bigint: value.toString() } : value;
/**
* "Reviver" method for JSON.parse's second argument. Converts objects having a single
* `$bigint: string` property to an actual `bigint` value.
*
* Inverse of {@link bigIntJsonStringifyReplacer}.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json
*/
const bigIntJsonParseReviver = (_key, value) => isPojo(value) && Object.keys(value).length === 1 && typeof value.$bigint === "string" ? BigInt(value.$bigint) : value;
//#endregion
//#region src/utils/formatQuery/defaultRuleGroupProcessorCEL.ts
/**
* Rule group processor used by {@link formatQuery} for "cel" format.
*
* @group Export
*/
const defaultRuleGroupProcessorCEL = (ruleGroup, options) => {
const { fields, fallbackExpression, getParseNumberBoolean, placeholderFieldName, placeholderOperatorName, placeholderValueName, ruleProcessor, validateRule, validationMap } = options;
const processRuleGroup = (rg, outermost) => {
var _rg$id;
if (!isRuleOrGroupValid(rg, validationMap[(_rg$id = rg.id) !== null && _rg$id !== void 0 ? _rg$id : ""])) return outermost ? fallbackExpression : "";
const expression = rg.rules.map((rule) => {
var _rule$valueSource;
if (typeof rule === "string") return celCombinatorMap[rule];
if (isRuleGroup(rule)) return processRuleGroup(rule);
const [validationResult, fieldValidator] = validateRule(rule);
if (!isRuleOrGroupValid(rule, validationResult, fieldValidator) || rule.field === placeholderFieldName || rule.operator === placeholderOperatorName || placeholderValueName !== void 0 && rule.value === placeholderValueName) return "";
const fieldData = getOption(fields, rule.field);
return ruleProcessor(rule, _objectSpread2(_objectSpread2({}, options), {}, {
parseNumbers: getParseNumberBoolean(fieldData === null || fieldData === void 0 ? void 0 : fieldData.inputType),
escapeQuotes: ((_rule$valueSource = rule.valueSource) !== null && _rule$valueSource !== void 0 ? _rule$valueSource : "value") === "value",
fieldData
}));
}).filter(Boolean).join(isRuleGroupType(rg) ? ` ${celCombinatorMap[rg.combinator]} ` : " ");
const [prefix, suffix] = rg.not || !outermost ? [`${rg.not ? "!" : ""}(`, ")"] : ["", ""];
return expression ? `${prefix}${expression}${suffix}` : fallbackExpression;
};
return processRuleGroup(ruleGroup, true);
};
//#endregion
//#region src/utils/formatQuery/defaultRuleProcessorCEL.ts
const shouldNegate$2 = (op) => op.startsWith("not") || op.startsWith("doesnot");
const escapeDoubleQuotes = (v, escapeQuotes) => typeof v !== "string" || !escapeQuotes ? v : v.replaceAll(`"`, `\\"`);
/**
* Default rule processor used by {@link formatQuery} for "cel" format.
*
* @group Export
*/
const defaultRuleProcessorCEL = (rule, opts = {}) => {
const { escapeQuotes, parseNumbers, preserveValueOrder } = opts;
const { field, operator, value, valueSource } = rule;
const valueIsField = valueSource === "field";
const operatorTL = lc(operator === "=" ? "==" : operator);
const useBareValue = typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || shouldRenderAsNumber(value, parseNumbers);
const matchEval = processMatchMode(rule);
if (matchEval === false) return "";
else if (matchEval) {
const { mode, threshold } = matchEval;
const arrayElementAlias = "elem_alias";
const celQuery = transformQuery(rule.value, { ruleProcessor: (r) => _objectSpread2(_objectSpread2({}, r), {}, { field: `${arrayElementAlias}${r.field ? `.${r.field}` : ""}` }) });
const nestedArrayFilter = defaultRuleGroupProcessorCEL(celQuery, opts);
switch (mode) {
case "all": return `${field}.all(${arrayElementAlias}, ${nestedArrayFilter})`;
case "none":
case "some": return `${mode === "none" ? "!" : ""}${field}.exists(${arrayElementAlias}, ${nestedArrayFilter})`;
case "atleast":
case "atmost":
case "exactly": {
const totalCount = `double(${field}.size())`;
const filteredCount = `${field}.filter(${arrayElementAlias}, ${nestedArrayFilter}).size()`;
const op = mode === "atleast" ? ">=" : mode === "atmost" ? "<=" : "==";
if (threshold > 0 && threshold < 1) return `${filteredCount} ${op} (${totalCount} * ${threshold})`;
return `${filteredCount} ${op} ${threshold}`;
}
}
}
switch (operatorTL) {
case "<":
case "<=":
case "==":
case "!=":
case ">":
case ">=": return `${field} ${operatorTL} ${valueIsField || useBareValue ? trimIfString(value) : `"${escapeDoubleQuotes(value, escapeQuotes)}"`}`;
case "contains":
case "doesnotcontain": {
const negate$1 = shouldNegate$2(operatorTL) ? "!" : "";
return `${negate$1}${field}.contains(${valueIsField ? trimIfString(value) : `"${escapeDoubleQuotes(value, escapeQuotes)}"`})`;
}
case "beginswith":
case "doesnotbeginwith": {
const negate$1 = shouldNegate$2(operatorTL) ? "!" : "";
return `${negate$1}${field}.startsWith(${valueIsField ? trimIfString(value) : `"${escapeDoubleQuotes(value, escapeQuotes)}"`})`;
}
case "ends