UNPKG

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
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