-
-
Notifications
You must be signed in to change notification settings - Fork 752
/
query.ts
158 lines (130 loc) · 4.48 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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import { _ } from '@feathersjs/commons'
import { BadRequest } from '@feathersjs/errors'
import { Query } from '@feathersjs/feathers'
import { FilterQueryOptions, FilterSettings, PaginationParams } from './declarations'
const parse = (value: any) => (typeof value !== 'undefined' ? parseInt(value, 10) : value)
const isPlainObject = (value: any) => _.isObject(value) && value.constructor === {}.constructor
const validateQueryProperty = (query: any, operators: string[] = []): Query => {
if (!isPlainObject(query)) {
return query
}
for (const key of Object.keys(query)) {
if (key.startsWith('$') && !operators.includes(key)) {
throw new BadRequest(`Invalid query parameter ${key}`, query)
}
const value = query[key]
if (isPlainObject(value)) {
query[key] = validateQueryProperty(value, operators)
}
}
return {
...query
}
}
const getFilters = (query: Query, settings: FilterQueryOptions) => {
const filterNames = Object.keys(settings.filters)
return filterNames.reduce(
(current, key) => {
const queryValue = query[key]
const filter = settings.filters[key]
if (filter) {
const value = typeof filter === 'function' ? filter(queryValue, settings) : queryValue
if (value !== undefined) {
current[key] = value
}
}
return current
},
{} as { [key: string]: any }
)
}
const getQuery = (query: Query, settings: FilterQueryOptions) => {
const keys = Object.keys(query).concat(Object.getOwnPropertySymbols(query) as any as string[])
return keys.reduce((result, key) => {
if (typeof key === 'string' && key.startsWith('$')) {
if (settings.filters[key] === undefined) {
throw new BadRequest(`Invalid filter value ${key}`)
}
} else {
result[key] = validateQueryProperty(query[key], settings.operators)
}
return result
}, {} as Query)
}
/**
* Returns the converted `$limit` value based on the `paginate` configuration.
* @param _limit The limit value
* @param paginate The pagination options
* @returns The converted $limit value
*/
export const getLimit = (_limit: any, paginate?: PaginationParams) => {
const limit = parse(_limit)
if (paginate && (paginate.default || paginate.max)) {
const base = paginate.default || 0
const lower = typeof limit === 'number' && !isNaN(limit) && limit >= 0 ? limit : base
const upper = typeof paginate.max === 'number' ? paginate.max : Number.MAX_VALUE
return Math.min(lower, upper)
}
return limit
}
export const OPERATORS = ['$in', '$nin', '$lt', '$lte', '$gt', '$gte', '$ne', '$or']
export const FILTERS: FilterSettings = {
$skip: (value: any) => parse(value),
$sort: (sort: any): { [key: string]: number } => {
if (typeof sort !== 'object' || Array.isArray(sort)) {
return sort
}
return Object.keys(sort).reduce(
(result, key) => {
result[key] = typeof sort[key] === 'object' ? sort[key] : parse(sort[key])
return result
},
{} as { [key: string]: number }
)
},
$limit: (_limit: any, { paginate }: FilterQueryOptions) => getLimit(_limit, paginate),
$select: (select: any) => {
if (Array.isArray(select)) {
return select.map((current) => `${current}`)
}
return select
},
$or: (or: any, { operators }: FilterQueryOptions) => {
if (Array.isArray(or)) {
return or.map((current) => validateQueryProperty(current, operators))
}
return or
},
$and: (and: any, { operators }: FilterQueryOptions) => {
if (Array.isArray(and)) {
return and.map((current) => validateQueryProperty(current, operators))
}
return and
}
}
/**
* Converts Feathers special query parameters and pagination settings
* and returns them separately as `filters` and the rest of the query
* as `query`. `options` also gets passed the pagination settings and
* a list of additional `operators` to allow when querying properties.
*
* @param query The initial query
* @param options Options for filtering the query
* @returns An object with `query` which contains the query without `filters`
* and `filters` which contains the converted values for each filter.
*/
export function filterQuery(_query: Query, options: FilterQueryOptions = {}) {
const query = _query || {}
const settings = {
...options,
filters: {
...FILTERS,
...options.filters
},
operators: OPERATORS.concat(options.operators || [])
}
return {
filters: getFilters(query, settings),
query: getQuery(query, settings)
}
}