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

Commit 561d0b9

Browse files
authored
refactor: Changes pagination and sorting constants to improve readability. (#8)
BREAKING CHANGE: In pagination `forward` was renamed to `direction`. BREAKING CHANGE: To paginate forward use `'forward'` in `pagination.direction` instead of `true` in `pagination.forward`. Use `import { forward } from '@js-entity-repos/core/dist/types/PaginationDirection'`. BREAKING CHANGE: To paginate backward use `'backward'` in `pagination.direction` instead of `false` in `pagination.forward`. Use `import { backward } from '@js-entity-repos/core/dist/types/PaginationDirection'`. BREAKING CHANGE: To sort in ascending order use `'asc'` in `sort` instead of `true`. Use `import { asc } from '@js-entity-repos/core/dist/types/SortOrder'`. BREAKING CHANGE: To sort in descending order use `'desc'` in `sort` instead of `false`. Use `import { desc } from '@js-entity-repos/core/dist/types/SortOrder'`.
1 parent ccb868d commit 561d0b9

File tree

10 files changed

+61
-37
lines changed

10 files changed

+61
-37
lines changed

Diff for: docs/options.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,13 @@ The filter below is comprehensive example using all of the operators.
101101

102102

103103
### Sort
104-
This is an object where a key represents the entity property to be sorted and the value represents the direction to sort. The value should be `true` to sort in ascending order and `false` to sort in descending order. The properties are sorted in order of their definition in the sort option, for example, the following sort option `{ createdAt: false, id: true }` will sort by the `createdAt` property first and then the the `id` property.
104+
This is an object where a key represents the entity property to be sorted and the value represents the direction to sort. The value should be `'asc'` to sort in ascending order and `'desc'` to sort in descending order. The properties are sorted in order of their definition in the sort option, for example, the following sort option `{ createdAt: false, id: true }` will sort by the `createdAt` property first and then the the `id` property.
105105

106-
This package contains the [TypeScript Sort type definition](../src/types/Sort.ts).
106+
This package contains the [TypeScript Sort type definition](../src/types/Sort.ts). It also contains [constants for `'asc'` and `'desc'`](../src/types/SortOrder.ts) that should always be used to avoid breaking changes in the future.
107107

108108
### Pagination
109-
This is an object with three properties, `limit`, `forward`, and `cursor`. The `limit` property defines how many entities to return (maximum). The `forward` property defines whether the entities should be iterated through forwards (when `true`) or backwards (when `false`) 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/).
109+
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/).
110110

111111
Concrete implementations of the facade can use the [`createCursorFromEntity`](../src/utils/createCursorFromEntity) and [`createPaginationFilter`](../src/utils/createPaginationFilter) utility functions to generate cursors.
112112

113-
This package also contains the [TypeScript Pagination interface](../src/types/Pagination.ts) and the [TypeScript Cursor type definition](../src/types/Cursor.ts).
113+
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: src/tests/getEntities/paginationTest.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as assert from 'power-assert';
33
import Facade from '../../Facade';
44
import Cursor from '../../types/Cursor';
55
import Pagination from '../../types/Pagination';
6+
import PaginationDirection, { backward, forward } from '../../types/PaginationDirection';
67
import { TestEntity, testEntity } from '../utils/testEntity';
78

89
export default (facade: Facade<TestEntity>) => {
@@ -16,8 +17,8 @@ export default (facade: Facade<TestEntity>) => {
1617
await facade.createEntity({ id: secondId, entity: secondEntity });
1718
};
1819

19-
const paginate = (cursor: Cursor, forward: boolean) => {
20-
const pagination: Pagination = { cursor, forward, limit: 1 };
20+
const paginate = (cursor: Cursor, direction: PaginationDirection) => {
21+
const pagination: Pagination = { cursor, direction, limit: 1 };
2122
return facade.getEntities({ pagination });
2223
};
2324

@@ -29,50 +30,50 @@ export default (facade: Facade<TestEntity>) => {
2930

3031
it('should return first entity when there are two entities limitted to 1', async () => {
3132
await createTestEntities();
32-
const pagination: Pagination = { cursor: undefined, forward: true, limit: 1 };
33+
const pagination: Pagination = { cursor: undefined, direction: forward, limit: 1 };
3334
const result = await facade.getEntities({ pagination });
3435
assert.deepEqual(result.entities, [firstEntity]);
3536
});
3637

3738
it('should return first entity when paginating forward without cursor', async () => {
3839
await createTestEntities();
39-
const finalResult = await paginate(undefined, true);
40+
const finalResult = await paginate(undefined, forward);
4041
assert.deepEqual(finalResult.entities, [firstEntity]);
4142
});
4243

4344
it('should return second entity when paginating forward with first cursor', async () => {
4445
await createTestEntities();
45-
const firstResult = await paginate(undefined, true);
46-
const finalResult = await paginate(firstResult.nextCursor, true);
46+
const firstResult = await paginate(undefined, forward);
47+
const finalResult = await paginate(firstResult.nextCursor, forward);
4748
assert.deepEqual(finalResult.entities, [secondEntity]);
4849
});
4950

5051
it('should return no entities when paginating forward with second cursor', async () => {
5152
await createTestEntities();
52-
const firstResult = await paginate(undefined, true);
53-
const secondResult = await paginate(firstResult.nextCursor, true);
54-
const finalResult = await paginate(secondResult.nextCursor, true);
53+
const firstResult = await paginate(undefined, forward);
54+
const secondResult = await paginate(firstResult.nextCursor, forward);
55+
const finalResult = await paginate(secondResult.nextCursor, forward);
5556
assert.deepEqual(finalResult.entities, []);
5657
});
5758

5859
it('should return second entity when paginating backward without cursor', async () => {
5960
await createTestEntities();
60-
const finalResult = await paginate(undefined, false);
61+
const finalResult = await paginate(undefined, backward);
6162
assert.deepEqual(finalResult.entities, [secondEntity]);
6263
});
6364

6465
it('should return first entity when paginating backward with first cursor', async () => {
6566
await createTestEntities();
66-
const firstResult = await paginate(undefined, false);
67-
const finalResult = await paginate(firstResult.previousCursor, false);
67+
const firstResult = await paginate(undefined, backward);
68+
const finalResult = await paginate(firstResult.previousCursor, backward);
6869
assert.deepEqual(finalResult.entities, [firstEntity]);
6970
});
7071

7172
it('should return no entities when paginating backward with second cursor', async () => {
7273
await createTestEntities();
73-
const firstResult = await paginate(undefined, false);
74-
const secondResult = await paginate(firstResult.previousCursor, false);
75-
const finalResult = await paginate(secondResult.previousCursor, false);
74+
const firstResult = await paginate(undefined, backward);
75+
const secondResult = await paginate(firstResult.previousCursor, backward);
76+
const finalResult = await paginate(secondResult.previousCursor, backward);
7677
assert.deepEqual(finalResult.entities, []);
7778
});
7879
};

Diff for: src/tests/getEntities/sortTest.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'mocha'; // tslint:disable-line:no-import-side-effect
22
import * as assert from 'power-assert';
33
import Facade from '../../Facade';
44
import Sort from '../../types/Sort';
5+
import { asc, desc } from '../../types/SortOrder';
56
import { TestEntity, testEntity } from '../utils/testEntity';
67

78
export default (facade: Facade<TestEntity>) => {
@@ -26,36 +27,36 @@ export default (facade: Facade<TestEntity>) => {
2627
it('should sort by one ascending property when entities are ordered', async () => {
2728
await facade.createEntity({ id: firstId, entity: firstEntity });
2829
await facade.createEntity({ id: secondId, entity: secondEntity });
29-
await assertSort([firstEntity, secondEntity], { stringProp: true });
30+
await assertSort([firstEntity, secondEntity], { stringProp: asc });
3031
});
3132

3233
it('should sort by one ascending property when entities are unordered', async () => {
3334
await facade.createEntity({ id: secondId, entity: secondEntity });
3435
await facade.createEntity({ id: firstId, entity: firstEntity });
35-
await assertSort([firstEntity, secondEntity], { stringProp: true });
36+
await assertSort([firstEntity, secondEntity], { stringProp: asc });
3637
});
3738

3839
it('should sort by one descending property when entities are ordered', async () => {
3940
await facade.createEntity({ id: secondId, entity: secondEntity });
4041
await facade.createEntity({ id: firstId, entity: firstEntity });
41-
await assertSort([secondEntity, firstEntity], { stringProp: false });
42+
await assertSort([secondEntity, firstEntity], { stringProp: desc });
4243
});
4344

4445
it('should sort by one descending property when entities are unordered', async () => {
4546
await facade.createEntity({ id: firstId, entity: firstEntity });
4647
await facade.createEntity({ id: secondId, entity: secondEntity });
47-
await assertSort([secondEntity, firstEntity], { stringProp: false });
48+
await assertSort([secondEntity, firstEntity], { stringProp: desc });
4849
});
4950

5051
it('should sort by two properties when ascending first and descending second', async () => {
5152
await facade.createEntity({ id: firstId, entity: firstEntity });
5253
await facade.createEntity({ id: secondId, entity: secondEntity });
53-
await assertSort([firstEntity, secondEntity], { stringProp: true, numberProp: false });
54+
await assertSort([firstEntity, secondEntity], { stringProp: asc, numberProp: desc });
5455
});
5556

5657
it('should sort by two properties when descending first and ascending second', async () => {
5758
await facade.createEntity({ id: firstId, entity: firstEntity });
5859
await facade.createEntity({ id: secondId, entity: secondEntity });
59-
await assertSort([secondEntity, firstEntity], { stringProp: false, numberProp: true });
60+
await assertSort([secondEntity, firstEntity], { stringProp: desc, numberProp: asc });
6061
});
6162
};

Diff for: src/types/Pagination.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Cursor from './Cursor';
2+
import PaginationDirection from './PaginationDirection';
23

34
export default interface Pagination {
45
readonly cursor: Cursor;
5-
readonly forward: boolean;
6+
readonly direction: PaginationDirection;
67
readonly limit: number;
78
}

Diff for: src/types/PaginationDirection.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const forward = 'forward';
2+
export const backward = 'backward';
3+
4+
type PaginationDirection = typeof forward | typeof backward;
5+
6+
export default PaginationDirection;

Diff for: src/types/Sort.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Entity from './Entity';
2+
import SortOrder from './SortOrder';
23

34
type Sort<E extends Entity> = {
4-
readonly [P in keyof E]?: boolean;
5+
readonly [P in keyof E]?: SortOrder;
56
};
67

78
export default Sort;

Diff for: src/types/SortOrder.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const asc = 'asc';
2+
export const desc = 'desc';
3+
4+
type SortOrder = typeof asc | typeof desc;
5+
6+
export default SortOrder;

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import 'mocha'; // tslint:disable-line:no-import-side-effect
22
import * as assert from 'power-assert';
33
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
4+
import { asc } from '../../types/SortOrder';
45
import createCursorFromEntity from './index';
56

67
describe('createCursorFromEntity', () => {
78
it('should return undefined when the entity is undefined', () => {
8-
const actualResult = createCursorFromEntity<TestEntity>(undefined, { id: true });
9+
const actualResult = createCursorFromEntity<TestEntity>(undefined, { id: asc });
910
assert.equal(actualResult, undefined);
1011
});
1112

1213
it('should return the correct cursor when the entity is defined', () => {
13-
const actualResult = createCursorFromEntity<TestEntity>(testEntity, { id: true });
14+
const actualResult = createCursorFromEntity<TestEntity>(testEntity, { id: asc });
1415
assert.equal(actualResult, 'eyJpZCI6InRlc3RfaWQifQ==');
1516
});
1617
});

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

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,40 @@ import * as assert from 'power-assert';
33
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
44
import { Filter } from '../../types/Filter';
55
import Pagination from '../../types/Pagination';
6+
import { backward, forward } from '../../types/PaginationDirection';
67
import Sort from '../../types/Sort';
8+
import { asc, desc } from '../../types/SortOrder';
79
import createCursorFromEntity from '../createCursorFromEntity';
810
import createPaginationFilter from './index';
911

1012
describe('createCursorFromEntity', () => {
11-
const sort: Sort<TestEntity> = { id: true, booleanProp: false };
13+
const sort: Sort<TestEntity> = { id: asc, numberProp: desc };
1214

1315
it('should return empty filter when the cursor is undefined', () => {
14-
const pagination: Pagination = { cursor: undefined, forward: true, limit: 1 };
16+
const pagination: Pagination = { cursor: undefined, direction: forward, limit: 1 };
1517
const actualResult = createPaginationFilter<TestEntity>(pagination, sort);
1618
const expectedResult = {};
1719
assert.deepEqual(actualResult, expectedResult);
1820
});
1921

2022
it('should return the correct filter when the cursor is defined and going forward', () => {
2123
const cursor = createCursorFromEntity<TestEntity>(testEntity, sort);
22-
const pagination: Pagination = { cursor, forward: true, limit: 1 };
24+
const pagination: Pagination = { cursor, direction: forward, limit: 1 };
2325
const actualResult = createPaginationFilter<TestEntity>(pagination, sort);
2426
const expectedResult: Filter<TestEntity> = {
25-
booleanProp: { $lte: testEntity.booleanProp },
2627
id: { $gt: testEntity.id },
28+
numberProp: { $lte: testEntity.numberProp },
2729
};
2830
assert.deepEqual(actualResult, expectedResult);
2931
});
3032

3133
it('should return the correct filter when the cursor is defined and going backward', () => {
3234
const cursor = createCursorFromEntity<TestEntity>(testEntity, sort);
33-
const pagination: Pagination = { cursor, forward: false, limit: 1 };
35+
const pagination: Pagination = { cursor, direction: backward, limit: 1 };
3436
const actualResult = createPaginationFilter<TestEntity>(pagination, sort);
3537
const expectedResult: Filter<TestEntity> = {
36-
booleanProp: { $gte: testEntity.booleanProp },
3738
id: { $lt: testEntity.id },
39+
numberProp: { $gte: testEntity.numberProp },
3840
};
3941
assert.deepEqual(actualResult, expectedResult);
4042
});

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import Entity from '../../types/Entity';
44
// tslint:disable-next-line:no-unused
55
import Filter, { ConditionFilter, EntityFilter } from '../../types/Filter';
66
import Pagination from '../../types/Pagination';
7+
import { forward } from '../../types/PaginationDirection';
78
import Sort from '../../types/Sort';
9+
import { asc } from '../../types/SortOrder';
810

911
const xor = (conditionA: boolean, conditionB: boolean) => {
1012
return (conditionA && !conditionB) || (!conditionA && conditionB);
@@ -16,8 +18,11 @@ export default <E extends Entity>(pagination: Pagination, sort: Sort<E>): Filter
1618
}
1719
const cursorObj = JSON.parse(atob(pagination.cursor));
1820
const filter = mapValues(cursorObj, (cursorValue, sortKey) => {
19-
const forward = !xor(get(sort, sortKey), pagination.forward);
20-
if (forward) {
21+
const ascendingPagination = !xor(
22+
get(sort, sortKey) === asc,
23+
pagination.direction === forward,
24+
);
25+
if (ascendingPagination) {
2126
if (sortKey === 'id') {
2227
return { $gt: cursorValue };
2328
} else {

0 commit comments

Comments
 (0)