Skip to content
This repository was archived by the owner on Jun 22, 2021. It is now read-only.

Commit 82180ff

Browse files
authored
feat: Adds convertPropertyFilter util. (#10)
1 parent 9b7b7a5 commit 82180ff

File tree

5 files changed

+160
-2
lines changed

5 files changed

+160
-2
lines changed

Diff for: docs/facade.md

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ The facade contains common functions for storage and retrieval of entities from
1111
- [removeEntities](./functions.md#removeentities)
1212
- [removeEntity](./functions.md#removeentity)
1313

14+
This package also contains some utility functions outside of the Facade that you might find useful.
15+
16+
- [convertPropertyFilter](./utils.md#convertPropertyFilter)
17+
- [createCursorFromEntity](./utils.md#createCursorFromEntity)
18+
- [createPaginationFilter](./utils.md#createPaginationFilter)
19+
1420
The [facade in this package is a TypeScript interface](../src/Facade.ts), but concrete implementations of the interface are listed below.
1521

1622
- [Memory](https://github.com/js-entity-repos/memory) - This is useful for testing client/server side.

Diff for: docs/options.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface TodoEntity extends Entity {
3838
This is an object containing some of the entity's properties. This package uses [TypeScript's Partial type](https://www.typescriptlang.org/docs/handbook/advanced-types.html) applied to an [Entity](#entity) (using `Partial<Entity>`).
3939

4040
### Filter
41-
This is an object that filters the entities. The [filter type definition](../src/types/Filter.ts) currently supports the following operators which have been [borrowed from Mongo](https://docs.mongodb.com/manual/reference/operator/query/).
41+
This is an object that filters the entities. The [filter type definition](../src/types/Filter.ts) currently supports the following operators which have been [borrowed from Mongo](https://docs.mongodb.com/manual/reference/operator/query/). In some cases (like dates) you may need to use the [`convertPropertyFilter` utility function](./utils.md#convertPropertyFilter).
4242

4343
Operator | Description
4444
--- | ---
@@ -110,6 +110,6 @@ This package contains the [TypeScript Sort type definition](../src/types/Sort.ts
110110
### Pagination
111111
This is an object with three properties, `limit`, `direction`, and `cursor`. The `limit` property defines how many entities to return (maximum). The `direction` property defines whether the entities should be iterated through forwards (when `'forward'`) or backwards (when `'backward'`) from the `cursor`. The `cursor` property defines where to start iterating through the entities. Cursors have been used instead of `skip` and `limit` to avoid the [pagination issues discussed by Rakhitha Nimesh](https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/).
112112

113-
Concrete implementations of the facade can use the [`createCursorFromEntity`](../src/utils/createCursorFromEntity) and [`createPaginationFilter`](../src/utils/createPaginationFilter) utility functions to generate cursors.
113+
Concrete implementations of the facade can use the [`createCursorFromEntity`](./utils.md#createCursorFromEntity) and [`createPaginationFilter`](./utils.md#createPaginationFilter) utility functions to generate cursors.
114114

115115
This package contains the [TypeScript Pagination interface](../src/types/Pagination.ts) and the [TypeScript Cursor type definition](../src/types/Cursor.ts). It also contains [constants for `'forward'` and `'backward'`](../src/types/PaginationDirection.ts) that should always be used to avoid breaking changes in the future.

Diff for: docs/utils.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Utils
2+
3+
This package contains some common utility functions that can be used by both users and implementors of the [Facade](./facade.md#facade).
4+
5+
- [convertPropertyFilter](#convertPropertyFilter)
6+
- [createCursorFromEntity](#createCursorFromEntity)
7+
- [createPaginationFilter](#createPaginationFilter)
8+
9+
### convertPropertyFilter
10+
Converts the filter value for a certain property name. For example, if you have a date property on your entity called `createdAt` storing the date at which an entity was created, then if you filter the property using a string (usually via [js-entity-repos/express](https://github.com/js-entity-repos/express)), you can use this util to ensure that the string is converted to a date before using the filter in the database. This is usually used by users of the js-entity-repos inside a `constructFilter` function in the factory config of [concrete implementations of the Facade](./facade.md#facade), the [Knex implementation allows this configuration function](https://github.com/js-entity-repos/knex#construct-the-facade) amongst others.
11+
12+
```ts
13+
import convertPropertyFilter from '@js-entity-repos/core/dist/utils/convertPropertyFilter';
14+
15+
convertPropertyFilter({
16+
converter: (value: any) => new Date(value),
17+
propertyName: 'createdAt',
18+
filter: { createdAt: '2018-01-01' },
19+
});
20+
// Returns the result of { createdAt: new Date('2018-01-01') }
21+
```
22+
23+
### createCursorFromEntity
24+
Exactly what it says on the tin, this creates a cursor from an entity. A cursor is constructed by creating a [filter](./options#filter) that will filter out entities not expected in the next set of paginated results. This filter is then stringified to JSON and base 64 encoded. This function is usually used by [concrete implementations of the Facade](./facade.md#facade) like [Knex's getEntities implementation](https://github.com/js-entity-repos/knex/blob/master/src/functions/getEntities.ts)..
25+
26+
```ts
27+
import createCursorFromEntity from '@js-entity-repos/core/dist/utils/createCursorFromEntity';
28+
import { asc } from '@js-entity-repos/core/dist/types/SortOrder';
29+
30+
createCursorFromEntity(undefined, { id: asc });
31+
// Returns undefined
32+
33+
createCursorFromEntity({
34+
booleanProp: true,
35+
id: 'test_id_1',
36+
numberProp: 1,
37+
stringProp: 'test_string_prop',
38+
}, {
39+
id: asc,
40+
});
41+
// Returns eyJpZCI6InRlc3RfaWQifQ==
42+
```
43+
44+
### createPaginationFilter
45+
Takes a [pagination option](./options#pagination) and a [sort option](./options#sort)) to produces a [filter](./options#filter) that can filter out entities not expected in the next set of paginated results. This function is usually used by [concrete implementations of the Facade](./facade.md#facade) like [Knex's getEntities implementation](https://github.com/js-entity-repos/knex/blob/master/src/functions/getEntities.ts).
46+
47+
```ts
48+
import createPaginationFilter from '@js-entity-repos/core/dist/utils/createPaginationFilter';
49+
import { asc, desc } from '@js-entity-repos/core/dist/types/SortOrder';
50+
import { backward, forward } from '@js-entity-repos/core/dist/types/PaginationDirection';
51+
52+
createPaginationFilter(
53+
{ cursor: undefined, direction: forward, limit: 1 },
54+
{ id: asc, numberProp: desc }
55+
);
56+
// Returns {}
57+
58+
createPaginationFilter(
59+
{ cursor: nextCursor, direction: forward, limit: 1 },
60+
{ id: asc, numberProp: desc }
61+
);
62+
// Returns the result of { id: { $gt: lastEntity.id }, numberProp: { $lte: lastEntity.numberProp } }
63+
64+
createPaginationFilter(
65+
{ cursor: prevCursor, direction: backward, limit: 1 },
66+
{ id: asc, numberProp: desc }
67+
);
68+
// Returns the result of { id: { $lt: firstEntity.id }, numberProp: { $gte: firstEntity.numberProp } }
69+
```

Diff for: src/utils/convertPropertyFilter/index.test.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'mocha'; // tslint:disable-line:no-import-side-effect
2+
import * as assert from 'power-assert';
3+
import { TestEntity } from '../../tests/utils/testEntity';
4+
import { Filter } from '../../types/Filter';
5+
import convertPropertyFilter from './index';
6+
7+
describe('convertPropertyFilter', () => {
8+
const originalValue = 10;
9+
const expectedValue = '10';
10+
const converter = (value: any) => value.toString();
11+
const propertyName = 'numberProp';
12+
13+
it('should convert the property value when used inside a prop filter', () => {
14+
const filter: Filter<TestEntity> = { numberProp: { $gt: originalValue } };
15+
const newFilter = convertPropertyFilter({ converter, filter, propertyName });
16+
const expectedFilter = { numberProp: { $gt: expectedValue } };
17+
assert.deepEqual(newFilter, expectedFilter);
18+
});
19+
20+
it('should convert the property value when used inside an entity filter', () => {
21+
const filter: Filter<TestEntity> = { numberProp: originalValue, stringProp: 'test_string' };
22+
const newFilter = convertPropertyFilter({ converter, filter, propertyName });
23+
const expectedFilter = { numberProp: expectedValue, stringProp: 'test_string' };
24+
assert.deepEqual(newFilter, expectedFilter);
25+
});
26+
27+
it('should convert the property value when used inside a condition filter', () => {
28+
const filter: Filter<TestEntity> = { $and: [{ numberProp: originalValue }] };
29+
const newFilter = convertPropertyFilter({ converter, filter, propertyName });
30+
const expectedFilter = { $and: [{ numberProp: expectedValue }] };
31+
assert.deepEqual(newFilter, expectedFilter);
32+
});
33+
});

Diff for: src/utils/convertPropertyFilter/index.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { isArray, isPlainObject, mapValues } from 'lodash';
2+
3+
export interface Opts {
4+
readonly propertyName: string;
5+
readonly converter: (propertyValue: any) => any;
6+
readonly filter: any;
7+
readonly useConverter?: boolean;
8+
}
9+
10+
const convertPropertyFilter = ({
11+
converter,
12+
filter,
13+
propertyName,
14+
useConverter = false,
15+
}: Opts): any => {
16+
if (isPlainObject(filter)) {
17+
return mapValues(filter, (subFilter, filterKey) => {
18+
if (filterKey !== propertyName) {
19+
return convertPropertyFilter({
20+
converter,
21+
filter: subFilter,
22+
propertyName,
23+
useConverter,
24+
});
25+
}
26+
return convertPropertyFilter({
27+
converter,
28+
filter: subFilter,
29+
propertyName,
30+
useConverter: true,
31+
});
32+
});
33+
}
34+
if (isArray(filter)) {
35+
return filter.map((subFilter) => {
36+
return convertPropertyFilter({
37+
converter,
38+
filter: subFilter,
39+
propertyName,
40+
useConverter,
41+
});
42+
});
43+
}
44+
if (!useConverter) {
45+
return filter;
46+
}
47+
return converter(filter);
48+
};
49+
50+
export default convertPropertyFilter;

0 commit comments

Comments
 (0)