From 031012e96bf99e1eb08c155059fd5106b38e9faf Mon Sep 17 00:00:00 2001 From: Dylan Stanfield Date: Thu, 22 Oct 2020 15:54:56 -0400 Subject: [PATCH] feat(core): added two new filter helpers --- .../docs/utilities/query-helpers.mdx | 125 +++++++++++--- packages/core/__tests__/helpers.spec.ts | 101 +++++++++++- .../core/src/helpers/comparison.builder.ts | 65 ++------ packages/core/src/helpers/filter.builder.ts | 12 +- packages/core/src/helpers/filter.helpers.ts | 154 ++++++++++++++++++ packages/core/src/helpers/index.ts | 5 +- packages/core/src/helpers/query.helpers.ts | 61 +------ packages/core/src/index.ts | 2 + 8 files changed, 374 insertions(+), 151 deletions(-) create mode 100644 packages/core/src/helpers/filter.helpers.ts diff --git a/documentation/docs/utilities/query-helpers.mdx b/documentation/docs/utilities/query-helpers.mdx index 9daf6d828..411f2ae44 100644 --- a/documentation/docs/utilities/query-helpers.mdx +++ b/documentation/docs/utilities/query-helpers.mdx @@ -5,7 +5,7 @@ title: Query Helpers The `@nestjs-query/core` package provides a number of helper functions to transform or apply queries to a list of items. An example use case for these helpers would be to write a `QueryService` that wraps a store that does not support the - query options natively (e.g. An in memory collection of objects such as a static array of objects). +query options natively (e.g. An in memory collection of objects such as a static array of objects). All examples will be based on the following DTO definition. @@ -23,19 +23,18 @@ interface TestDTO { } ``` - ## applyFilter The `applyFilter` helper applies a `Filter` to a single object or an array of objects. ### Arguments -* `dto: DTO|DTO[]` - * If a single object a function that will test the dto against the filter, returning `true` when if it matches the - filter. - * If an array of objects is provided the array will be filtered returning a new array with all elements that match - the filter. -* `filter: Filter` - The filter to check the object[s] against. See [`Filtering`](../concepts/queries.mdx#filtering) +- `dto: DTO|DTO[]` + - If a single object a function that will test the dto against the filter, returning `true` when if it matches the + filter. + - If an array of objects is provided the array will be filtered returning a new array with all elements that match + the filter. +- `filter: Filter` - The filter to check the object[s] against. See [`Filtering`](../concepts/queries.mdx#filtering) ### Example @@ -63,7 +62,7 @@ The `applySort` will sort an array of dtos. :::info Because `applySort` uses the native `Array#sort` method it may not exactly match the ordering you would expect from a - database. +database. ::: :::warning @@ -72,8 +71,9 @@ represented as strings the applySort method may not work as expected. ::: ### Arguments -* `dto: DTO[]` - The array of DTOs to sort. -* `sortFields: SortField[]` - The sorting criteria. See [`Sorting`](../concepts/queries.mdx#sorting) + +- `dto: DTO[]` - The array of DTOs to sort. +- `sortFields: SortField[]` - The sorting criteria. See [`Sorting`](../concepts/queries.mdx#sorting) ### Example @@ -111,8 +111,9 @@ The resulting sorted array would be. The `applyPaging` method will apply a `limit` and/or `offset` to an array of dtos. ### Arguments -* `dto: DTO[]` - The array of DTOs to page. -* `paging: Paging` - The paging arguments to apply. See [`Paging`](../concepts/queries.mdx#paging) + +- `dto: DTO[]` - The array of DTOs to page. +- `paging: Paging` - The paging arguments to apply. See [`Paging`](../concepts/queries.mdx#paging) ### Example @@ -142,8 +143,9 @@ The `applyQuery` uses the `applyFilter`, `applySorting`, and `applyPaging` metho DTOs. ### Arguments -* `dto: DTO[]` - The array of DTOs to page. -* `query: Query` - The query to apply to the array of dtos. See [`Queries`](../concepts/queries.mdx) + +- `dto: DTO[]` - The array of DTOs to page. +- `query: Query` - The query to apply to the array of dtos. See [`Queries`](../concepts/queries.mdx) ### Example @@ -179,9 +181,9 @@ The transformFilter is used to remap fields in a `Filter`. This method is common ### Arguments -* `filter: Filter` - The filter you want to transform. -* `fieldMap: QueryFieldMap` - A map of fields where the key is a key in the From type, and the value is a -key in the to type. +- `filter: Filter` - The filter you want to transform. +- `fieldMap: QueryFieldMap` - A map of fields where the key is a key in the From type, and the value is a + key in the to type. ### Example @@ -218,13 +220,13 @@ The new filter would be ## transformSort -The `transformSort` is used to remap fields in an array of `SortField[]`. This method is commonly used when +The `transformSort` is used to remap fields in an array of `SortField[]`. This method is commonly used when defining a custom [Assembler](../concepts/advanced/assemblers.mdx). ### Arguments -* `sortFields: SortField[]` - The array of sorting criteria to transform. -* `fieldMap: QueryFieldMap` - A map of fields where the key is a key in the From type, and the value is a key in the to type. +- `sortFields: SortField[]` - The array of sorting criteria to transform. +- `fieldMap: QueryFieldMap` - A map of fields where the key is a key in the From type, and the value is a key in the to type. ### Example @@ -246,25 +248,25 @@ const dtoSort: SortField[] = [ { field: 'last', direction: SortDirection.ASC }, ]; -const transformed = transformSort(dtoSort, fieldMap); +const transformed = transformSort(dtoSort, fieldMap); ``` ```ts [ { field: 'firstName', direction: SortDirection.DESC }, { field: 'lastName', direction: SortDirection.ASC }, -] +]; ``` ## transformQuery The `transformQuery` method uses the `transformFilter` and `transformSort` methods to remap a `Query`. This method is - commonly used when defining a custom [Assembler](../concepts/advanced/assemblers.mdx). +commonly used when defining a custom [Assembler](../concepts/advanced/assemblers.mdx). ### Arguments -* `sortFields: Query` - The query to transform. -* `fieldMap: QueryFieldMap` - A map of fields where the key is a key in the From type, and the value is a key in the to type. +- `sortFields: Query` - The query to transform. +- `fieldMap: QueryFieldMap` - A map of fields where the key is a key in the From type, and the value is a key in the to type. ### Example @@ -291,6 +293,7 @@ const dtoQuery: Query = { const transformed = transformQuery(dtoQuery, fieldMap); ``` + The resulting query would be. ```ts @@ -302,3 +305,73 @@ The resulting query would be. ] } ``` + +## getFilterComparisons + +Used to search a filter get a list of comparison objects for a given key. + +### Arguments + +- `filter: Filter` - The filter to search. +- `key: keyof DTO` - The key in the DTO object to search for in the filter object. + +### Example + +```ts +import { Filter, getFilterComparisons } from `@nestjs-query/core`; + +class TestDTO { + age!: number; + + title!: string; +} + +const filter: Filter = { + age: { gte: 10 }, + or: [{ title: { like: '%bar' } }, { title: { eq: 'foobar' } }], +}; + +const comparisons = getFilterComparisons(filter, 'title'); +``` + +The resulting array would be + +```ts +[{ like: '%bar' }, { eq: 'foobar' }]; +``` + +## getFilterOmitting + +Used to get a filter with a given key removed. + +### Arguments + +- `filter: Filter` - The filter containing the unwanted key. +- `key: keyof DTO` - The key in the DTO object to remove in the filter object. + +### Example + +```ts +import { Filter, getFilterOmitting } from `@nestjs-query/core`; + +class TestDTO { + age!: number; + + title!: string; +} + +const filter: Filter = { + age: { gte: 10 }, + or: [{ title: { like: '%bar' } }, { title: { eq: 'foobar' } }], +}; + +const filterWithoutTitle = getFilterOmitting(filter, 'title'); +``` + +The resulting filter would be + +```ts +{ + age: { gte: 10 }, +} +``` diff --git a/packages/core/__tests__/helpers.spec.ts b/packages/core/__tests__/helpers.spec.ts index 03a43538d..22b2de8ef 100644 --- a/packages/core/__tests__/helpers.spec.ts +++ b/packages/core/__tests__/helpers.spec.ts @@ -16,8 +16,11 @@ import { transformFilter, transformQuery, transformSort, + getFilterOmitting, + getFilterFields, + getFilterComparisons, + mergeFilter, } from '../src'; -import { getFilterFields } from '../src/helpers/query.helpers'; import { AggregateQuery } from '../src/interfaces/aggregate-query.interface'; class TestDTO { @@ -1517,3 +1520,99 @@ describe('applyQuery', () => { }); }); }); + +describe('getFilterComparisons', () => { + type Foo = { + bar: number; + baz: number; + }; + + it('should get list of comparisons from a filter given a key', () => { + const f0: Filter = {}; + const f1: Filter = { + bar: { gt: 0 }, + baz: { gt: 1 }, + }; + const f2: Filter = { + bar: { gt: 0 }, + baz: { gt: 1 }, + and: [{ baz: { lt: 2 }, bar: { lt: 3 } }], + }; + const f3: Filter = { + bar: { gt: 0 }, + baz: { gt: 1 }, + or: [{ baz: { lt: 4 }, bar: { lt: 5 } }], + }; + const f4: Filter = { + bar: { gt: 0 }, + baz: { gt: 1 }, + and: [{ baz: { lt: 2 }, bar: { lt: 3 } }], + or: [{ baz: { lt: 4 }, bar: { lt: 5 } }], + }; + expect(getFilterComparisons(f0, 'bar')).toEqual(expect.arrayContaining([])); + expect(getFilterComparisons(f1, 'bar')).toEqual(expect.arrayContaining([{ gt: 0 }])); + expect(getFilterComparisons(f2, 'bar')).toEqual(expect.arrayContaining([{ gt: 0 }, { lt: 3 }])); + expect(getFilterComparisons(f3, 'bar')).toEqual(expect.arrayContaining([{ gt: 0 }, { lt: 5 }])); + expect(getFilterComparisons(f4, 'bar')).toEqual(expect.arrayContaining([{ gt: 0 }, { lt: 3 }, { lt: 5 }])); + }); +}); + +describe('getFilterOmitting', () => { + type Foo = { + bar: number; + baz: number; + }; + + it('should omit a key from a filter', () => { + const filter: Filter = { + bar: { gt: 0 }, + baz: { gt: 0 }, + and: [{ baz: { lt: 100 }, bar: { lt: 100 } }], + or: [{ baz: { lt: 100 }, bar: { lt: 100 } }], + }; + expect(getFilterOmitting(filter, 'baz')).toEqual({ + bar: { gt: 0 }, + and: [{ bar: { lt: 100 } }], + or: [{ bar: { lt: 100 } }], + }); + }); + + it('should delete and and or properties if they are empty after omitting', () => { + const filter: Filter = { + bar: { gt: 0 }, + baz: { gt: 0 }, + and: [{ baz: { lt: 100 } }], + or: [{ baz: { lt: 100 } }], + }; + expect(getFilterOmitting(filter, 'baz')).toEqual({ + bar: { gt: 0 }, + }); + }); +}); + +describe('mergeFilter', () => { + type Foo = { + bar: number; + baz: number; + }; + + it('should merge two filters', () => { + const f1: Filter = { + bar: { gt: 0 }, + }; + const f2: Filter = { + baz: { gt: 0 }, + }; + expect(mergeFilter(f1, f2)).toEqual({ + and: expect.arrayContaining([f1, f2]), + }); + }); + + it('should noop if one of the filters is empty', () => { + const filter: Filter = { + bar: { gt: 0 }, + }; + expect(mergeFilter(filter, {})).toEqual(filter); + expect(mergeFilter({}, filter)).toEqual(filter); + }); +}); diff --git a/packages/core/src/helpers/comparison.builder.ts b/packages/core/src/helpers/comparison.builder.ts index a3faebaa5..83115b187 100644 --- a/packages/core/src/helpers/comparison.builder.ts +++ b/packages/core/src/helpers/comparison.builder.ts @@ -1,61 +1,22 @@ -import { - CommonFieldComparisonBetweenType, - FilterComparisonOperators, - Filter, - FilterFieldComparison, -} from '../interfaces'; +import { CommonFieldComparisonBetweenType, FilterComparisonOperators } from '../interfaces'; import { ComparisonField, FilterFn } from './types'; - -type LikeComparisonOperators = 'like' | 'notLike' | 'iLike' | 'notILike'; -type InComparisonOperators = 'in' | 'notIn'; -type BetweenComparisonOperators = 'between' | 'notBetween'; -type RangeComparisonOperators = 'gt' | 'gte' | 'lt' | 'lte'; -type BooleanComparisonOperators = 'eq' | 'neq' | 'is' | 'isNot'; +import { + isBooleanComparisonOperators, + isRangeComparisonOperators, + isInComparisonOperators, + isLikeComparisonOperator, + isBetweenComparisonOperators, + BooleanComparisonOperators, + RangeComparisonOperators, + LikeComparisonOperators, + InComparisonOperators, + BetweenComparisonOperators, +} from './filter.helpers'; const compare = (filter: (dto: DTO) => boolean, fallback: boolean): FilterFn => { return (dto?: DTO) => (dto ? filter(dto) : fallback); }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const isLikeComparisonOperator = (op: any): op is LikeComparisonOperators => { - return op === 'like' || op === 'notLike' || op === 'iLike' || op === 'notILike'; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const isInComparisonOperators = (op: any): op is InComparisonOperators => { - return op === 'in' || op === 'notIn'; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const isBetweenComparisonOperators = (op: any): op is BetweenComparisonOperators => { - return op === 'between' || op === 'notBetween'; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const isRangeComparisonOperators = (op: any): op is RangeComparisonOperators => { - return op === 'gt' || op === 'gte' || op === 'lt' || op === 'lte'; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const isBooleanComparisonOperators = (op: any): op is BooleanComparisonOperators => { - return op === 'eq' || op === 'neq' || op === 'is' || op === 'isNot'; -}; - -export const isComparison = ( - maybeComparison: FilterFieldComparison | Filter, -): maybeComparison is FilterFieldComparison => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return Object.keys(maybeComparison as Record).every((op) => { - return ( - isLikeComparisonOperator(op) || - isInComparisonOperators(op) || - isBetweenComparisonOperators(op) || - isRangeComparisonOperators(op) || - isBooleanComparisonOperators(op) - ); - }); -}; - export class ComparisonBuilder { static build( field: F, diff --git a/packages/core/src/helpers/filter.builder.ts b/packages/core/src/helpers/filter.builder.ts index 911d2df8d..7b4f7e9a4 100644 --- a/packages/core/src/helpers/filter.builder.ts +++ b/packages/core/src/helpers/filter.builder.ts @@ -1,6 +1,7 @@ import { Filter, FilterComparisons, FilterFieldComparison } from '../interfaces'; -import { ComparisonBuilder, isComparison } from './comparison.builder'; +import { ComparisonBuilder } from './comparison.builder'; import { ComparisonField, FilterFn } from './types'; +import { getFilterFieldComparison, isComparison } from './filter.helpers'; export class FilterBuilder { static build(filter: Filter): FilterFn { @@ -36,13 +37,6 @@ export class FilterBuilder { ); } - private static getField>( - obj: FilterComparisons, - field: K, - ): FilterFieldComparison & Filter { - return obj[field] as FilterFieldComparison & Filter; - } - private static withFilterComparison( field: T, cmp: FilterFieldComparison, @@ -56,7 +50,7 @@ export class FilterBuilder { } private static withComparison(filter: FilterComparisons, fieldOrNested: keyof DTO): FilterFn { - const value = this.getField(filter, fieldOrNested); + const value = getFilterFieldComparison(filter, fieldOrNested); if (isComparison(value)) { return this.withFilterComparison(fieldOrNested, value); } diff --git a/packages/core/src/helpers/filter.helpers.ts b/packages/core/src/helpers/filter.helpers.ts new file mode 100644 index 000000000..fd053b9b8 --- /dev/null +++ b/packages/core/src/helpers/filter.helpers.ts @@ -0,0 +1,154 @@ +import { Filter, FilterFieldComparison, FilterComparisons } from '../interfaces'; +import { QueryFieldMap } from './query.helpers'; +import { FilterBuilder } from './filter.builder'; + +export type LikeComparisonOperators = 'like' | 'notLike' | 'iLike' | 'notILike'; +export type InComparisonOperators = 'in' | 'notIn'; +export type BetweenComparisonOperators = 'between' | 'notBetween'; +export type RangeComparisonOperators = 'gt' | 'gte' | 'lt' | 'lte'; +export type BooleanComparisonOperators = 'eq' | 'neq' | 'is' | 'isNot'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isLikeComparisonOperator = (op: any): op is LikeComparisonOperators => { + return op === 'like' || op === 'notLike' || op === 'iLike' || op === 'notILike'; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isInComparisonOperators = (op: any): op is InComparisonOperators => { + return op === 'in' || op === 'notIn'; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isBetweenComparisonOperators = (op: any): op is BetweenComparisonOperators => { + return op === 'between' || op === 'notBetween'; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isRangeComparisonOperators = (op: any): op is RangeComparisonOperators => { + return op === 'gt' || op === 'gte' || op === 'lt' || op === 'lte'; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isBooleanComparisonOperators = (op: any): op is BooleanComparisonOperators => { + return op === 'eq' || op === 'neq' || op === 'is' || op === 'isNot'; +}; + +export const isComparison = ( + maybeComparison?: FilterFieldComparison | Filter, +): maybeComparison is FilterFieldComparison => { + if (!maybeComparison) { + return false; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return Object.keys(maybeComparison as Record).every((op) => { + return ( + isLikeComparisonOperator(op) || + isInComparisonOperators(op) || + isBetweenComparisonOperators(op) || + isRangeComparisonOperators(op) || + isBooleanComparisonOperators(op) + ); + }); +}; + +// TODO: test +export const getFilterFieldComparison = >( + obj: FilterComparisons, + field: K, +): FilterFieldComparison & Filter => { + return obj[field] as FilterFieldComparison & Filter; +}; + +export const transformFilter = ( + filter: Filter | undefined, + fieldMap: QueryFieldMap, +): Filter | undefined => { + if (!filter) { + return undefined; + } + return Object.keys(filter).reduce((newFilter, filterField) => { + if (filterField === 'and' || filterField === 'or') { + return { ...newFilter, [filterField]: filter[filterField]?.map((f) => transformFilter(f, fieldMap)) }; + } + const fromField = filterField as keyof From; + const otherKey = fieldMap[fromField]; + if (!otherKey) { + throw new Error(`No corresponding field found for '${filterField}' when transforming Filter`); + } + return { ...newFilter, [otherKey as string]: filter[fromField] }; + }, {} as Filter); +}; + +export const mergeFilter = (base: Filter, source: Filter): Filter => { + if (!Object.keys(base).length) { + return source; + } + if (!Object.keys(source).length) { + return base; + } + return { and: [source, base] } as Filter; +}; + +export const getFilterFields = (filter: Filter): string[] => { + const fieldSet: Set = Object.keys(filter).reduce((fields: Set, filterField: string): Set => { + if (filterField === 'and' || filterField === 'or') { + const andOrFilters = filter[filterField]; + if (andOrFilters !== undefined) { + return andOrFilters.reduce((andOrFields, andOrFilter) => { + return new Set([...andOrFields, ...getFilterFields(andOrFilter)]); + }, fields); + } + } else { + fields.add(filterField); + } + return fields; + }, new Set()); + return [...fieldSet]; +}; + +export const getFilterComparisons = >( + filter: Filter, + key: K, +): FilterFieldComparison[] => { + const results: FilterFieldComparison[] = []; + if (filter.and || filter.or) { + const filters = [...(filter.and ?? []), ...(filter.or ?? [])]; + filters.forEach((f) => getFilterComparisons(f, key).forEach((comparison) => results.push(comparison))); + } + const comparison = getFilterFieldComparison(filter as FilterComparisons, key); + if (isComparison(comparison)) { + results.push(comparison); + } + return [...results]; +}; + +export const getFilterOmitting = (filter: Filter, key: keyof DTO): Filter => { + return Object.keys(filter).reduce>((f, next) => { + const omitted = { ...f }; + const k = next as keyof Filter; + if (k === 'and' && filter.and) { + omitted.and = filter.and.map((part) => getFilterOmitting(part, key)); + if (omitted.and.every((part) => Object.keys(part).length === 0)) { + delete omitted.and; + } + } else if (k === 'or' && filter.or) { + omitted.or = filter.or.map((part) => getFilterOmitting(part, key)); + if (omitted.or.every((part) => Object.keys(part).length === 0)) { + delete omitted.or; + } + } else if (k !== key) { + omitted[k] = filter[k]; + } + return omitted; + }, {} as Filter); +}; + +export function applyFilter(dto: DTO[], filter: Filter): DTO[]; +export function applyFilter(dto: DTO, filter: Filter): boolean; +export function applyFilter(dtoOrArray: DTO | DTO[], filter: Filter): boolean | DTO[] { + const filterFunc = FilterBuilder.build(filter); + if (Array.isArray(dtoOrArray)) { + return dtoOrArray.filter((dto) => filterFunc(dto)); + } + return filterFunc(dtoOrArray); +} diff --git a/packages/core/src/helpers/index.ts b/packages/core/src/helpers/index.ts index 24294ae1b..c47b84504 100644 --- a/packages/core/src/helpers/index.ts +++ b/packages/core/src/helpers/index.ts @@ -1,15 +1,12 @@ export { - applyFilter, mergeQuery, - mergeFilter, QueryFieldMap, - transformFilter, transformQuery, transformSort, - getFilterFields, applySort, applyPaging, applyQuery, invertSort, } from './query.helpers'; export { transformAggregateQuery, transformAggregateResponse } from './aggregate.helpers'; +export * from './filter.helpers'; diff --git a/packages/core/src/helpers/query.helpers.ts b/packages/core/src/helpers/query.helpers.ts index cfc3280be..6d0bc4b5f 100644 --- a/packages/core/src/helpers/query.helpers.ts +++ b/packages/core/src/helpers/query.helpers.ts @@ -1,8 +1,8 @@ import merge from 'lodash.merge'; -import { Filter, Paging, Query, SortDirection, SortField, SortNulls } from '../interfaces'; -import { FilterBuilder } from './filter.builder'; +import { Paging, Query, SortDirection, SortField, SortNulls } from '../interfaces'; import { SortBuilder } from './sort.builder'; import { PageBuilder } from './page.builder'; +import { transformFilter, applyFilter } from './filter.helpers'; export type QueryFieldMap = { [F in keyof From]?: T; @@ -24,26 +24,6 @@ export const transformSort = ( }); }; -export const transformFilter = ( - filter: Filter | undefined, - fieldMap: QueryFieldMap, -): Filter | undefined => { - if (!filter) { - return undefined; - } - return Object.keys(filter).reduce((newFilter, filterField) => { - if (filterField === 'and' || filterField === 'or') { - return { ...newFilter, [filterField]: filter[filterField]?.map((f) => transformFilter(f, fieldMap)) }; - } - const fromField = filterField as keyof From; - const otherKey = fieldMap[fromField]; - if (!otherKey) { - throw new Error(`No corresponding field found for '${filterField}' when transforming Filter`); - } - return { ...newFilter, [otherKey as string]: filter[fromField] }; - }, {} as Filter); -}; - export const transformQuery = (query: Query, fieldMap: QueryFieldMap): Query => { return { filter: transformFilter(query.filter, fieldMap), @@ -52,47 +32,10 @@ export const transformQuery = (query: Query, fieldMap: QueryFiel }; }; -export const mergeFilter = (base: Filter, source: Filter): Filter => { - if (!Object.keys(base).length) { - return source; - } - if (!Object.keys(source).length) { - return base; - } - return { and: [source, base] } as Filter; -}; - export const mergeQuery = (base: Query, source: Query): Query => { return merge(base, source); }; -export const getFilterFields = (filter: Filter): string[] => { - const fieldSet: Set = Object.keys(filter).reduce((fields: Set, filterField: string): Set => { - if (filterField === 'and' || filterField === 'or') { - const andOrFilters = filter[filterField]; - if (andOrFilters !== undefined) { - return andOrFilters.reduce((andOrFields, andOrFilter) => { - return new Set([...andOrFields, ...getFilterFields(andOrFilter)]); - }, fields); - } - } else { - fields.add(filterField); - } - return fields; - }, new Set()); - return [...fieldSet]; -}; - -export function applyFilter(dto: DTO[], filter: Filter): DTO[]; -export function applyFilter(dto: DTO, filter: Filter): boolean; -export function applyFilter(dtoOrArray: DTO | DTO[], filter: Filter): boolean | DTO[] { - const filterFunc = FilterBuilder.build(filter); - if (Array.isArray(dtoOrArray)) { - return dtoOrArray.filter((dto) => filterFunc(dto)); - } - return filterFunc(dtoOrArray); -} - export const applySort = (dtos: DTO[], sortFields: SortField[]): DTO[] => { return SortBuilder.build(sortFields)(dtos); }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4af30fbad..6ecd2bdce 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -24,6 +24,8 @@ export { mergeQuery, mergeFilter, invertSort, + getFilterComparisons, + getFilterOmitting, } from './helpers'; export { ClassTransformerAssembler,