Skip to content

Commit

Permalink
feat(core): added two new filter helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Dylan Stanfield authored and doug-martin committed Oct 23, 2020
1 parent 5546a6c commit 031012e
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 151 deletions.
125 changes: 99 additions & 26 deletions documentation/docs/utilities/query-helpers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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<DTO>` - 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<DTO>` - The filter to check the object[s] against. See [`Filtering`](../concepts/queries.mdx#filtering)

### Example

Expand Down Expand Up @@ -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
Expand All @@ -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<DTO>[]` - The sorting criteria. See [`Sorting`](../concepts/queries.mdx#sorting)
- `dto: DTO[]` - The array of DTOs to sort.
- `sortFields: SortField<DTO>[]` - The sorting criteria. See [`Sorting`](../concepts/queries.mdx#sorting)
### Example
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<DTO>` - The query to apply to the array of dtos. See [`Queries`](../concepts/queries.mdx)
- `dto: DTO[]` - The array of DTOs to page.
- `query: Query<DTO>` - The query to apply to the array of dtos. See [`Queries`](../concepts/queries.mdx)
### Example
Expand Down Expand Up @@ -179,9 +181,9 @@ The transformFilter is used to remap fields in a `Filter`. This method is common
### Arguments
* `filter: Filter<From>` - The filter you want to transform.
* `fieldMap: QueryFieldMap<From, To>` - 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<From>` - The filter you want to transform.
- `fieldMap: QueryFieldMap<From, To>` - 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
Expand Down Expand Up @@ -218,13 +220,13 @@ The new filter would be
## transformSort
The `transformSort` is used to remap fields in an array of `SortField<DTO>[]`. This method is commonly used when
The `transformSort` is used to remap fields in an array of `SortField<DTO>[]`. This method is commonly used when
defining a custom [Assembler](../concepts/advanced/assemblers.mdx).
### Arguments
* `sortFields: SortField<From>[]` - The array of sorting criteria to transform.
* `fieldMap: QueryFieldMap<From, To>` - 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<From>[]` - The array of sorting criteria to transform.
- `fieldMap: QueryFieldMap<From, To>` - 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
Expand All @@ -246,25 +248,25 @@ const dtoSort: SortField<TestDTO>[] = [
{ 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<From>` - The query to transform.
* `fieldMap: QueryFieldMap<From, To>` - 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<From>` - The query to transform.
- `fieldMap: QueryFieldMap<From, To>` - 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
Expand All @@ -291,6 +293,7 @@ const dtoQuery: Query<TestDTO> = {

const transformed = transformQuery(dtoQuery, fieldMap);
```
The resulting query would be.
```ts
Expand All @@ -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<DTO>` - 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<TestDTO> = {
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<DTO>` - 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<TestDTO> = {
age: { gte: 10 },
or: [{ title: { like: '%bar' } }, { title: { eq: 'foobar' } }],
};

const filterWithoutTitle = getFilterOmitting(filter, 'title');
```
The resulting filter would be
```ts
{
age: { gte: 10 },
}
```
101 changes: 100 additions & 1 deletion packages/core/__tests__/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Foo> = {};
const f1: Filter<Foo> = {
bar: { gt: 0 },
baz: { gt: 1 },
};
const f2: Filter<Foo> = {
bar: { gt: 0 },
baz: { gt: 1 },
and: [{ baz: { lt: 2 }, bar: { lt: 3 } }],
};
const f3: Filter<Foo> = {
bar: { gt: 0 },
baz: { gt: 1 },
or: [{ baz: { lt: 4 }, bar: { lt: 5 } }],
};
const f4: Filter<Foo> = {
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<Foo> = {
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<Foo> = {
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<Foo> = {
bar: { gt: 0 },
};
const f2: Filter<Foo> = {
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<Foo> = {
bar: { gt: 0 },
};
expect(mergeFilter(filter, {})).toEqual(filter);
expect(mergeFilter({}, filter)).toEqual(filter);
});
});
Loading

0 comments on commit 031012e

Please sign in to comment.