Skip to content

Instantly share code, notes, and snippets.

@xxzefgh
Last active February 15, 2021 10:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xxzefgh/81f0629ce595de22f9ee4a52f4284951 to your computer and use it in GitHub Desktop.
Save xxzefgh/81f0629ce595de22f9ee4a52f4284951 to your computer and use it in GitHub Desktop.
enum QuerySelectors {
Eq = "$eq",
Gt = "$gt",
Gte = "$gte",
Lt = "$lt",
Lte = "$lte",
Ne = "$ne"
}
enum LogicalOperators {
And = "$and",
// Not = "$not",
Nor = "$nor",
Or = "$or"
}
type ComparableValue = string | number | boolean | null;
type Variant1 = {
[key: string]:
| ComparableValue
| { [selector in QuerySelectors]?: ComparableValue };
};
type Variant2 = { [operator in LogicalOperators]?: Array<Variant1 | Variant2> };
type FilterQuery = Variant1 | Variant2;
const querySelectors: string[] = Object.values(QuerySelectors);
const logicalOperators: string[] = Object.values(LogicalOperators);
function applyImplicitAnd(query: FilterQuery): FilterQuery {
return {
$and: [query]
};
}
function filterCollection<T>(
query: FilterQuery,
items: Array<Record<string, T>>
): Array<Record<string, T>> {
const filterFn = filterItem(applyImplicitAnd(query));
return items.filter(item => filterFn(item));
}
function filterItem(query: FilterQuery) {
return function filterItemInner(item: Record<string, any>): boolean {
return filterItemRecursive(query, item);
};
}
function filterItemRecursive(
query: FilterQuery,
item: Record<string, any>
): boolean {
const fieldsOrOps = Object.keys(query);
return fieldsOrOps.every(fieldOrOp => {
if (logicalOperators.includes(fieldOrOp)) {
const opQuery: FilterQuery[] = query[fieldOrOp];
switch (fieldOrOp as LogicalOperators) {
case LogicalOperators.And: {
return opQuery.reduce(
(result, q) => result && filterItemRecursive(q, item),
true
);
}
case LogicalOperators.Nor: {
return opQuery.reduce(
(result, q) => result && !filterItemRecursive(q, item),
true
);
}
case LogicalOperators.Or: {
return !!opQuery.find(q => filterItemRecursive(q, item));
}
}
} else {
if (isObject(query[fieldOrOp])) {
const fieldOps = Object.keys(query[fieldOrOp]);
return fieldOps.every(fieldOp => {
const compareTo = query[fieldOrOp][fieldOp];
switch (fieldOp) {
case QuerySelectors.Eq: {
return item[fieldOrOp] === compareTo;
}
case QuerySelectors.Gt: {
return item[fieldOrOp] > compareTo;
}
case QuerySelectors.Gte: {
return item[fieldOrOp] >= compareTo;
}
case QuerySelectors.Lt: {
return item[fieldOrOp] < compareTo;
}
case QuerySelectors.Lte: {
return item[fieldOrOp] <= compareTo;
}
case QuerySelectors.Ne: {
return item[fieldOrOp] !== compareTo;
}
}
});
} else {
return item[fieldOrOp] === query[fieldOrOp];
}
}
});
}
function isObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment