@comodinx/query-filters
Version:
@comodinx/query-filters is a module for parsing filters in string to object.
192 lines (191 loc) • 7.29 kB
JavaScript
"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;