UNPKG

@comodinx/query-filters

Version:

@comodinx/query-filters is a module for parsing filters in string to object.

192 lines (191 loc) 7.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Formatter = void 0; const constants_1 = require("./constants"); const helpers_1 = require("./helpers"); class Formatter { constructor(options = {}) { this.options = { ...constants_1.defaultOptions, ...(options || {}) }; const ops = []; // Compile regexp for operators this.options.operators.forEach((operator) => ops.push(`${this.options.operatorPrefix}${this.toStringOperator(operator)}${this.options.operatorSuffix}`)); // Instantiate new regexp with key + operators + value this.regexpOperatorPrefix = new RegExp(`^${this.options.operatorPrefix}`); this.regexpOperatorSuffix = new RegExp(`${this.options.operatorSuffix}$`); this.regexpFilters = new RegExp(`^(${this.options.key})(${ops.join("|")})(${this.options.value})$`, this.options.operatorFlags); } format(filters) { if (!filters || !Object.keys(filters).length) { return; } const expr = this.formatNode(filters); if (!expr || !expr.length) { return; } return expr; } formatNode(node) { if (!node || !(0, helpers_1.isObject)(node)) { return; } const orKeys = this.getOrKeys(); const andKeys = this.getAndKeys(); const parts = []; // 1) Plain fields (implicit AND) const plain = this.formatPlainObject(node, new Set([...orKeys, ...andKeys])); if (plain) { parts.push(plain); } // 2) OR block const orArr = this.pickLogicalArray(node, orKeys); if (orArr) { const orExpr = orArr .map((child) => this.formatNode(child)) .filter(Boolean) .join(this.options.logicals.or); if (orExpr) { parts.push(this.wrapGroup(orExpr)); } } // 3) AND block (rare, but supported) const andArr = this.pickLogicalArray(node, andKeys); if (andArr) { const andExpr = andArr .map((child) => this.formatNode(child)) .filter(Boolean) .join(this.options.logicals.and ?? this.options.separator); if (andExpr) { parts.push(andExpr); } } if (!parts.length) { return; } if (parts.length === 1) { return parts[0]; } return parts.join(this.options.logicals.and ?? this.options.separator); } formatPlainObject(filters, excludeKeys) { if (!filters || !(0, helpers_1.isObject)(filters)) { return; } const symbols = new Set([]); if ((0, helpers_1.isObject)(this.options.mapOperator)) { (0, helpers_1.each)(this.options.mapOperator, (operator) => { if ((0, helpers_1.isSymbol)(operator)) { symbols.add(operator); } }); } const parts = []; for (const rawKey of Object.keys(filters)) { if (excludeKeys.has(rawKey)) { continue; } const content = filters[rawKey]; if (!content || !(0, helpers_1.isObject)(content)) { continue; } const operatorKeys = [ ...Object.getOwnPropertySymbols(content).filter((s) => symbols.has(s)), ...Object.keys(content) ]; for (const opKey of operatorKeys) { const value = content[opKey]; if (value === undefined) { continue; } const mappedKey = this.mapKey(rawKey, opKey, constants_1.methodFormat).toString(); const mappedOperator = this.toStringOperator(this.mapOperator(opKey, constants_1.methodFormat)); const mappedValue = this.mapValue(value, opKey, constants_1.methodFormat); const filter = `${mappedKey}${this.options.operatorPrefix}${mappedOperator}${this.options.operatorSuffix}${mappedValue}`; if (this.regexpFilters.test(filter)) { parts.push(filter); } } } if (!parts.length) { return; } return parts.join(this.options.logicals.and ?? this.options.separator); } wrapGroup(expr) { return `${this.options.groups.start}${expr}${this.options.groups.end}`; } getOrKeys() { const configured = this.options.logicals?.orKey; return Array.from(new Set([configured, "or", "OR"].filter(Boolean))); } getAndKeys() { const configured = this.options.logicals?.andKey; return Array.from(new Set([configured, "and", "AND"].filter(Boolean))); } pickLogicalArray(node, keys) { for (const k of keys) { if (Object.prototype.hasOwnProperty.call(node, k)) { const v = node[k]; if (Array.isArray(v)) { return v; } } } } mapKey(key, operator, method) { let mapper = this.options.mapKey; if (!mapper) { mapper = this.options.mapKeyFormat; } if ((0, helpers_1.isFunction)(mapper)) { return mapper(key, operator, method); } if ((0, helpers_1.isObject)(mapper)) { return mapper[key] ?? key; } return key; } mapValue(value, operator, method) { let mapper = this.options.mapValue; if (!mapper) { mapper = this.options.mapValueFormat; } if ((0, helpers_1.isFunction)(mapper)) { return mapper(value, operator, method); } if ((0, helpers_1.isObject)(mapper)) { return mapper[value?.toString?.() ?? value] ?? value; } if (Array.isArray(value)) { return `[${value.join(this.options.separatorGroups)}]`; } if ((0, helpers_1.isObject)(value)) { return JSON.stringify(value); } return value; } mapOperator(operator, method) { this.options.mapOperator = this.options.mapOperator || this.options.mapper; if (!this.options.mapOperator) { return operator; } if ((0, helpers_1.isFunction)(this.options.mapOperator)) { return this.options.mapOperator(operator, method); } if (method === constants_1.methodFormat) { if (!this.options.mapOperatorInverse && (0, helpers_1.isObject)(this.options.mapOperator)) { this.options.mapOperatorInverse = (0, helpers_1.invert)(this.options.mapOperator); } return this.options.mapOperatorInverse?.[operator] || operator; } return this.options.mapOperator?.[operator] || operator; } toStringOperator(operator) { return ((0, helpers_1.isSymbol)(operator) ? Symbol.keyFor(operator) : operator) .toString() .trim() .toLowerCase() .replace(this.regexpOperatorPrefix, "") .replace(this.regexpOperatorSuffix, ""); } } exports.Formatter = Formatter;