Skip to content

Commit c12655d

Browse files
committed
feat(filters): control logical groups external mach mode
1 parent ff5ecf5 commit c12655d

File tree

4 files changed

+32
-15
lines changed

4 files changed

+32
-15
lines changed

src/query.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { GenericObject, QueryFilter, QueryFilterGroup, QueryParams, QueryResult } from './types'
2-
import { getObjectProperty, processFilterWithLookup, processSearchQuery } from './utils'
1+
import type { FilterOptions, GenericObject, QueryFilter, QueryFilterGroup, QueryParams, QueryResult } from './types'
2+
import { getObjectProperty, getOperator, processFilterWithLookup, processSearchQuery } from './utils'
33

44
export function query<T extends GenericObject, P extends QueryParams<T>>(
55
data: T[],
@@ -33,17 +33,19 @@ function matchesSearch<T extends GenericObject>(item: T, search?: QueryParams<T>
3333
})
3434
}
3535

36-
function matchesFilters<T extends GenericObject>(item: T, filters?: (QueryFilter | QueryFilterGroup)[]): boolean {
37-
if (!filters || filters.length === 0)
36+
function matchesFilters<T extends GenericObject>(item: T, filters?: FilterOptions): boolean {
37+
const _filters = (typeof filters === 'object' && ('groups' in filters) ? filters.groups : filters) ?? []
38+
if (!_filters || _filters.length === 0)
3839
return true
39-
const isGroup = filters.every(filter => 'filters' in filter)
40-
const method = isGroup ? 'some' : 'every'
41-
return filters.filter(filter => filter.condition?.() ?? true)[method]((group: QueryFilter | QueryFilterGroup) => {
40+
const isGroup = _filters.every(filter => 'filters' in filter)
41+
const groupOperator = getOperator(typeof filters === 'object' && 'operator' in filters ? filters.operator : 'OR')
42+
const method = isGroup ? (groupOperator === 'AND' ? 'every' : 'some') : 'every'
43+
return _filters.filter(filter => filter.condition?.() ?? true)[method]((group: QueryFilter | QueryFilterGroup) => {
4244
const groupFilters = 'filters' in group ? group.filters : [group]
4345
const op = 'filters' in group ? group.operator : 'OR'
4446
return groupFilters.filter(filter => filter.condition?.() ?? true)[op === 'AND' ? 'every' : 'some']((filter: QueryFilter) => {
4547
const value = getObjectProperty(item, filter.key)
46-
const operator = typeof filter.operator === 'function' ? filter.operator() : filter.operator ?? 'OR'
48+
const operator = getOperator(filter.operator)
4749
const params = (!('params' in filter) ? null : typeof filter.params === 'function' ? filter.params(filter.value) : filter.params) ?? null
4850
return processFilterWithLookup({
4951
type: filter.matchMode,
@@ -113,7 +115,7 @@ function* lazySortedQuery<T extends GenericObject>(
113115

114116
function paginateQuery<T extends GenericObject, P extends QueryParams<T>>(data: Iterable<T>, params: P): QueryResult<T, P> {
115117
if (typeof params.limit === 'undefined') {
116-
return { rows: Array.from(data) } as QueryResult<T, P>
118+
return Array.from(data) as QueryResult<T, P>
117119
}
118120

119121
else {

src/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ export interface QueryFilterGroup<Paths extends string = string> {
8888
condition?: () => boolean
8989
}
9090

91-
export type FilterOptions<Paths extends string = string> = Array<QueryFilterGroup<Paths>> | Array<QueryFilter<Paths>>
91+
export type FilterOptions<Paths extends string = string> = Array<QueryFilterGroup<Paths>> | Array<QueryFilter<Paths>> | {
92+
groups: Array<QueryFilterGroup<Paths>>
93+
operator: Operator
94+
}
9295

9396
export interface SearchOptions<Paths extends string = string> {
9497
value: string
@@ -114,7 +117,7 @@ export interface QueryParams<
114117
page?: number
115118
}
116119

117-
export type QueryResult<T extends GenericObject, P extends QueryParams<T>> = P extends { limit: number } ? { totalRows: number, totalPages: number, rows: T[], unpaginatedRows: T[] } : { rows: T[] }
120+
export type QueryResult<T extends GenericObject, P extends QueryParams<T>> = P extends { limit: number } ? { totalRows: number, totalPages: number, rows: T[], unpaginatedRows: T[] } : T[]
118121

119122
export interface MatchModeProcessorMap {
120123
equals: (f: { value: any, filter: any }) => boolean

src/utils.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FilterMatchMode, GenericObject, MatchModeProcessorMap } from './types'
1+
import type { FilterMatchMode, GenericObject, MatchModeProcessorMap, Operator } from './types'
22

33
export function getObjectProperty(object: Record<string, any>, key: string) {
44
return key.split('.').reduce((o, i) => o?.[i], object)
@@ -141,3 +141,13 @@ export function processFilterWithLookup<
141141

142142
return false
143143
}
144+
145+
export function getOperator(operator?: Operator) {
146+
return (typeof operator === 'function' ? operator() : operator) ?? 'OR'
147+
}
148+
149+
export function omit<T extends object, K extends Array<keyof T>>(object: T, keys: K) {
150+
const _result = { ...object }
151+
for (const key of keys) delete _result[key]
152+
return _result as Omit<T, K[number]>
153+
}

test/index.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-console */
22
import { describe, expect, it } from 'vitest'
33
import { query } from '../src'
4+
import { omit } from '../src/utils'
45
import PaginationFixtures from './fixtures/pagination.fixture.json'
56
import SortingFixtures from './fixtures/sorting.fixture.json'
67
import FilteringFixtures from './fixtures/filtering.fixture.json'
@@ -29,10 +30,11 @@ for (const fixture of fixtures) {
2930
describe(`${fixture.key} tests`, () => {
3031
for (const test of fixture.tests) {
3132
it(test.title, () => {
32-
const { unpaginatedRows, ...result } = (query as any)(test.data, test.query)
33+
const result = (query as any)(test.data, test.query)
34+
const matchResult = Array.isArray(result) ? { rows: result } : omit(result, ['unpaginatedRows'])
3335
console.log('expected', JSON.stringify(test.result, null, 2))
34-
console.log('actual', JSON.stringify(result, null, 2))
35-
expect(result).toEqual(test.result)
36+
console.log('actual', JSON.stringify(matchResult, null, 2))
37+
expect(matchResult).toEqual(test.result)
3638
})
3739
}
3840
})

0 commit comments

Comments
 (0)