@wener/miniquery
Version:
SQL Where like **safe** filter expression for ORM.
53 lines (48 loc) • 2 kB
text/typescript
import { match } from 'ts-pattern';
import type { Expr, SearchExpr, Value } from './types';
export function formatSearch(input: SearchExpr) {
const OP = { match: ':', eq: ':=', ne: ':!=', gt: ':>', lt: ':<', gte: ':>=', lte: ':<=', range: ':' } as const;
const _exprs = (s: SearchExpr): string => {
return s.map(_expr).join(' ');
};
const _expr = (s: Expr): string => {
return match(s)
.with({ type: 'keyword' }, ({ value, exact, negative }) => {
return `${negative ? '-' : ''}${exact ? `"${value}"` : value}`;
})
.with({ type: 'logical' }, ({ operator, value }) => value.map(_expr).join(` ${operator.toLocaleUpperCase()} `))
.with({ type: 'not' }, ({ value }) => `NOT ${_expr(value)}`)
.with({ type: 'compare' }, ({ field, operator, value, negative, mention }) => {
return `${negative ? '-' : ''}${mention ? '@' : ''}${field}${OP[operator]}${_value(value)}`;
})
.with({ type: 'comment' }, ({ value }) => `/* ${value} */`)
.with({ type: 'parentheses' }, ({ value }) => `(${_exprs(value)})`)
.exhaustive();
};
const _literal = (s: string | null | number) => {
if (typeof s === 'string') {
return s.includes(' ') ? `"${s}"` : s;
}
return JSON.stringify(s);
};
const _value = (v: Value): string => {
return match(v)
.with({ type: 'range' }, ({ minimum, maximum, minimumExclusive, maximumExclusive }) => {
if (minimumExclusive === undefined && maximumExclusive === undefined) {
let min = minimum === undefined ? '*' : _value(minimum);
let max = maximum === undefined ? '*' : _value(maximum);
return `${min}..${max}`;
}
let min = minimum === undefined ? '' : _value(minimum);
let max = maximum === undefined ? '' : _value(maximum);
return `${minimumExclusive ? '(' : '['}${min},${max}${maximumExclusive ? ')' : ']'}`;
})
.with({ format: 'mention' }, ({ value }) => {
return `@${value}`;
})
.otherwise((value) => {
return _literal(value.value);
});
};
return _exprs(input);
}