-
Notifications
You must be signed in to change notification settings - Fork 0
/
Query.ts
81 lines (69 loc) · 1.85 KB
/
Query.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
type RuleWithField = {
field: string
operator: string
value: any
logicalOperator?: LogicalOperator
rules?: never
}
type RuleWithRules = {
field?: never
operator?: never
value?: never
logicalOperator: LogicalOperator
rules: Rule[]
}
export type Rule = RuleWithField | RuleWithRules
type LogicalOperator = "AND" | "OR" | "NOT"
export class Query {
private rules: Rule[] = []
private logicalOperator: LogicalOperator = "AND"
addRule(rule: Rule) {
this.rules.push(rule)
}
addRules(rules: Rule[]) {
this.rules.push(...rules)
}
setLogicalOperator(operator: LogicalOperator) {
this.logicalOperator = operator
}
execute(collection: any[]): any[] {
return collection.filter((item) =>
this.evaluate(item, this.rules, this.logicalOperator),
)
}
private resolveField(item: any, field: string): any {
return field.split(".").reduce((obj, prop) => obj && obj[prop], item)
}
private evaluate(
item: any,
rules: Rule[],
logicalOperator: LogicalOperator,
): boolean {
return rules.reduce((acc, rule) => {
const result = rule?.rules
? this.evaluate(item, rule.rules, rule.logicalOperator!)
: this.compare(item, rule)
return logicalOperator === "AND"
? acc && result
: logicalOperator === "OR"
? acc || result
: !result
}, logicalOperator === "AND")
}
private compare(item: any, rule: RuleWithField): boolean {
const { field, operator, value } = rule
const fieldValue = this.resolveField(item, field!)
switch (operator) {
case "==":
return fieldValue == value
case ">":
return fieldValue > value
case "<":
return fieldValue < value
case "contains":
return fieldValue.includes(value)
default:
throw new Error(`Unsupported operator: ${operator}`)
}
}
}