1- import type { FilterMatchMode , GenericObject , MatchModeProcessorMap , QueryFilter , QueryFilterGroup , QueryParams , QueryResult } from './types'
2- import { MatchModeProcessor , getObjectProperty , validateBetweenPayload } from './utils'
1+ import type { GenericObject , QueryFilter , QueryFilterGroup , QueryParams , QueryResult } from './types'
2+ import { getObjectProperty , processFilterWithLookup , processSearchQuery } from './utils'
33
44export function query < T extends GenericObject , P extends QueryParams < T > > (
55 data : T [ ] ,
66 params : P ,
77) : QueryResult < T , P > {
8- let result : T [ ] = [ ... data ]
8+ let result = Array . from ( lazyQuery ( data , params ) )
99
10- if ( params . search && params . search . value ) {
11- result = result . filter ( ( item ) => {
12- for ( const key of params . search ?. keys ?? [ ] ) {
13- const field = typeof key === 'string' ? key : key . key
14- const caseSensitive = typeof key === 'string' ? ( params . search ?. caseSensitive ?? false ) : key . caseSensitive ?? false
15- if ( processSearchQuery ( { key : field , caseSensitive, object : item , value : params . search ! . value } ) )
16- return true
17- }
18- return false
19- } )
20- }
21-
22- if ( params . sort ) {
23- const sortArray = Array . isArray ( params . sort ) ? params . sort : [ params . sort ]
24-
25- result = result . sort ( ( a , b ) => {
26- for ( const { key, dir, parser } of sortArray ) {
27- const parserHandler = typeof parser === 'function' ? parser : ( v : any ) => parser === 'number' ? Number ( v ) : parser === 'boolean' ? Boolean ( v ) : parser === 'string' ? String ( v ) : v
28- const aParsed = parserHandler ( getObjectProperty ( a , key ) ) ?? null
29- const bParsed = parserHandler ( getObjectProperty ( b , key ) ) ?? null
30-
31- if ( aParsed !== bParsed ) {
32- const comparison = ( aParsed < bParsed ) ? - 1 : 1
33- return dir === 'asc' ? comparison : - comparison
34- }
35- }
36- return 0
37- } )
38- }
39-
40- if ( Array . isArray ( params . filter ) && params . filter . length ) {
41- result = result . filter ( ( item ) => {
42- const IS_GROUP = params . filter ?. every ( filter => 'filters' in filter ) ?? false
43- const METHOD = IS_GROUP ? 'some' : 'every' as const
44- const FILTERS = ( params ?. filter ?? [ ] ) as any [ ]
45- return FILTERS [ METHOD ] ( ( group : QueryFilter | QueryFilterGroup ) => {
46- const filters = 'filters' in group ? group . filters : [ group ]
47- const op = 'filters' in group ? group . operator : 'OR'
48- return filters [ op === 'AND' ? 'every' : 'some' as const ] ( ( filter ) => {
49- const value = getObjectProperty ( item , filter . key )
50- const operator = typeof filter . operator === 'function' ? filter . operator ( ) : filter . operator ?? 'OR'
51- if ( filter . matchMode === 'equals' ) {
52- return processFilterWithLookup ( {
53- type : 'equals' ,
54- params : null ,
55- operator,
56- value,
57- filter : filter . value ,
58- } )
59- }
60-
61- if ( filter . matchMode === 'contains' ) {
62- return processFilterWithLookup ( {
63- type : 'contains' ,
64- params : null ,
65- operator,
66- value,
67- filter : filter . value ,
68- } )
69- }
70- if ( filter . matchMode === 'between' ) {
71- return processFilterWithLookup ( {
72- type : 'between' ,
73- params : filter ?. params ?? null ,
74- operator,
75- value,
76- filter : filter . value ,
77- } )
78- }
79-
80- if ( filter . matchMode === 'greaterThan' ) {
81- return processFilterWithLookup ( {
82- type : 'greaterThan' ,
83- params : filter ?. params ?? null ,
84- operator,
85- value,
86- filter : filter . value ,
87- } )
88- }
89-
90- if ( filter . matchMode === 'greaterThanOrEqual' ) {
91- return processFilterWithLookup ( {
92- type : 'greaterThanOrEqual' ,
93- params : filter ?. params ?? null ,
94- operator,
95- value,
96- filter : filter . value ,
97- } )
98- }
99-
100- if ( filter . matchMode === 'lessThan' ) {
101- return processFilterWithLookup ( {
102- type : 'lessThan' ,
103- params : filter ?. params ?? null ,
104- operator,
105- value,
106- filter : filter . value ,
107- } )
108- }
109-
110- if ( filter . matchMode === 'lessThanOrEqual' ) {
111- return processFilterWithLookup ( {
112- type : 'lessThanOrEqual' ,
113- params : filter ?. params ?? null ,
114- operator,
115- value,
116- filter : filter . value ,
117- } )
118- }
119-
120- if ( filter . matchMode === 'exists' ) {
121- return processFilterWithLookup ( {
122- type : 'exists' ,
123- params : null ,
124- operator,
125- value,
126- filter : filter . value ,
127- } )
128- }
129-
130- if ( filter . matchMode === 'arrayLength' ) {
131- return processFilterWithLookup ( {
132- type : 'arrayLength' ,
133- params : null ,
134- operator,
135- value,
136- filter : filter . value ,
137- } )
138- }
139-
140- if ( filter . matchMode === 'regex' ) {
141- return processFilterWithLookup ( {
142- type : 'regex' ,
143- params : filter ?. params ?? null ,
144- operator,
145- value,
146- filter : filter . value ,
147- } )
148- }
149-
150- if ( filter . matchMode === 'objectMatch' ) {
151- const params = typeof filter . params === 'function' ? filter . params ( filter . value ) : filter . params
152- const filterValue = params ?. transformFilterValue ?.( filter . value ) ?? filter . value
153- return processFilterWithLookup ( {
154- type : 'objectMatch' ,
155- params,
156- operator,
157- value : params ?. applyAtRoot ? item : value ,
158- filter : filterValue ,
159- } )
160- }
161-
162- return false
163- } )
164- } )
165- } )
166- }
10+ if ( params . sort )
11+ result = sortedQuery ( result , params . sort )
16712
16813 if ( typeof params . limit === 'undefined' ) {
16914 return { rows : result } as QueryResult < T , P >
@@ -186,88 +31,68 @@ export function query<T extends GenericObject, P extends QueryParams<T>>(
18631 }
18732}
18833
189- function parseSearchValue ( value : any , caseSensitive : boolean ) : string {
190- return ( caseSensitive ? value ?. toString ( ) : value ?. toString ( ) ?. toLowerCase ?.( ) ) ?? ''
34+ function * lazyQuery < T extends GenericObject > ( data : T [ ] , params : QueryParams < T > ) : Generator < T > {
35+ for ( const item of data ) {
36+ if ( matchesSearchAndFilters ( item , params ) ) {
37+ yield item
38+ }
39+ }
19140}
19241
193- function processSearchQuery ( params : { key : string , object : Record < string , any > , value : string , caseSensitive : boolean } ) : boolean {
194- const { key, object, value, caseSensitive } = params
195- const keys = key . split ( '.' )
196-
197- let current : any = object
198- for ( let i = 0 ; i < keys . length ; i ++ ) {
199- if ( Array . isArray ( current ) )
200- return current . some ( item => processSearchQuery ( { key : keys . slice ( i ) . join ( '.' ) , object : item , value, caseSensitive } ) )
201-
202- else if ( current && Object . prototype . hasOwnProperty . call ( current , keys [ i ] ) )
203- current = current [ keys [ i ] ]
204-
205- else
206- return false
207- }
42+ function matchesSearchAndFilters < T extends GenericObject > ( item : T , params : QueryParams < T > ) : boolean {
43+ return matchesSearch ( item , params . search ) && matchesFilters ( item , params . filter )
44+ }
20845
209- if ( Array . isArray ( current ) )
210- return current . some ( element => parseSearchValue ( element , caseSensitive ) . includes ( parseSearchValue ( value , caseSensitive ) ) )
46+ function matchesSearch < T extends GenericObject > ( item : T , search ?: QueryParams < T > [ 'search' ] ) : boolean {
47+ if ( ! search || ! search . value )
48+ return true
21149
212- else
213- return parseSearchValue ( current , caseSensitive ) . includes ( parseSearchValue ( value , caseSensitive ) ) ?? false
50+ return search . keys . some ( ( key ) => {
51+ const field = typeof key === 'string' ? key : key . key
52+ const caseSensitive = typeof key === 'string' ? ( search . caseSensitive ?? false ) : key . caseSensitive ?? false
53+ return processSearchQuery ( { key : field , caseSensitive, object : item , value : search . value } )
54+ } )
21455}
21556
216- function processFilterWithLookup <
217- T extends FilterMatchMode ,
218- P = Parameters < MatchModeProcessorMap [ T ] > [ 0 ] ,
219- > ( params : {
220- type : FilterMatchMode
221- operator : 'AND' | 'OR'
222- value : any
223- filter : any
224- params : P extends { params : infer U } ? U : P extends { params ?: infer U } ? U : null
225- lookupFrom ?: 'value' | 'filter'
226- } ) {
227- if ( ! Array . isArray ( params . filter ) || ( params . type === 'between' && validateBetweenPayload ( params . filter ) ) ) {
228- return Array . isArray ( params . value )
229- ? params . value . some ( value =>
230- MatchModeProcessor [ params . type ] ( {
231- params : params . params as any ,
232- value,
233- filter : params . filter ,
234- } ) ,
235- )
236- : MatchModeProcessor [ params . type ] ( { params : params . params as any , value : params . value , filter : params . filter } )
237- }
238-
239- else if ( params . operator === 'AND' ) {
240- return Array . isArray ( params . filter ) && params . filter . every ( ( filter , index ) => {
241- if ( Array . isArray ( params . value ) ) {
242- return params . value . some ( value =>
243- MatchModeProcessor [ params . type ] ( {
244- params : params . params as any ,
245- value,
246- filter,
247- index,
248- } ) ,
249- )
250- }
251- else {
252- return MatchModeProcessor [ params . type ] ( { params : params . params as any , value : params . value , filter, index } )
253- }
57+ function matchesFilters < T extends GenericObject > ( item : T , filters ?: ( QueryFilter | QueryFilterGroup ) [ ] ) : boolean {
58+ if ( ! filters || filters . length === 0 )
59+ return true
60+ const isGroup = filters . every ( filter => 'filters' in filter )
61+ const method = isGroup ? 'some' : 'every'
62+ return filters [ method ] ( ( group : QueryFilter | QueryFilterGroup ) => {
63+ const groupFilters = 'filters' in group ? group . filters : [ group ]
64+ const op = 'filters' in group ? group . operator : 'OR'
65+ return groupFilters [ op === 'AND' ? 'every' : 'some' ] ( ( filter : QueryFilter ) => {
66+ const value = getObjectProperty ( item , filter . key )
67+ const operator = typeof filter . operator === 'function' ? filter . operator ( ) : filter . operator ?? 'OR'
68+ const params = ( ! ( 'params' in filter ) ? null : typeof filter . params === 'function' ? filter . params ( filter . value ) : filter . params ) ?? null
69+ return processFilterWithLookup ( {
70+ type : filter . matchMode ,
71+ params,
72+ operator,
73+ value,
74+ filter : filter . value ,
75+ } )
25476 } )
255- }
77+ } )
78+ }
25679
257- else if ( params . operator === 'OR' ) {
258- return Array . isArray ( params . filter ) && params . filter . some ( ( filter , index ) =>
259- Array . isArray ( params . value )
260- ? params . value . some ( value =>
261- MatchModeProcessor [ params . type ] ( {
262- params : params . params as any ,
263- value,
264- filter,
265- index,
266- } ) ,
267- )
268- : MatchModeProcessor [ params . type ] ( { params : params . params as any , value : params . value , filter, index } ) ,
269- )
270- }
80+ function sortedQuery < T extends GenericObject > ( data : T [ ] , sortOptions ?: QueryParams < T > [ 'sort' ] ) : T [ ] {
81+ if ( ! sortOptions )
82+ return data
27183
272- return false
84+ const sortArray = Array . isArray ( sortOptions ) ? sortOptions : [ sortOptions ]
85+ return data . slice ( ) . sort ( ( a , b ) => {
86+ for ( const { key, dir, parser } of sortArray ) {
87+ const parserHandler = typeof parser === 'function' ? parser : ( v : any ) => parser === 'number' ? Number ( v ) : parser === 'boolean' ? Boolean ( v ) : parser === 'string' ? String ( v ) : v
88+ const aParsed = parserHandler ( getObjectProperty ( a , key ) ) ?? null
89+ const bParsed = parserHandler ( getObjectProperty ( b , key ) ) ?? null
90+
91+ if ( aParsed !== bParsed ) {
92+ const comparison = ( aParsed < bParsed ) ? - 1 : 1
93+ return dir === 'asc' ? comparison : - comparison
94+ }
95+ }
96+ return 0
97+ } )
27398}
0 commit comments