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

Commit e9e670a

Browse files
authored
feat: Adds support for loading new results. (#13)
BREAKING CHANGE: Removes `PaginationFilterError`. BREAKING CHANGE: Removes `createEndCursorResult`. BREAKING CHANGE: Changes `createCursorFromEntity` params. BREAKING CHANGE: Changes `createGetEntitiesResult` return value. BREAKING CHANGE: Removes `end` from `Cursor`. BREAKING CHANGE: Changes the return type in the signature of `getEntities`. BREAKING CHANGE: Renames `previousCursor` to `backwardCursor` in `getEntities` result. BREAKING CHANGE: Renames `nextCursor` to `forwardCursor` in `getEntities` result.
1 parent 16be9e1 commit e9e670a

File tree

17 files changed

+370
-145
lines changed

17 files changed

+370
-145
lines changed

Diff for: docs/facade.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ This package also contains some utility functions outside of the Facade that you
1515

1616
- [convertPropertyFilter](./utils.md#convertpropertyfilter)
1717
- [createCursorFromEntity](./utils.md#createcursorfromentity)
18+
- [createCursorsFromEntities](./utils.md#createcursorsfromentities)
19+
- [createGetEntitiesResult](./utils.md#creategetentitiesresult)
1820
- [createPaginationFilter](./utils.md#createpaginationfilter)
1921

2022
The [facade in this package is a TypeScript interface](../src/Facade.ts), but concrete implementations of the interface are listed below.

Diff for: docs/functions.md

+19-12
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,29 @@ Retreives a sorted paginated array of entities that match the [`filter`](./optio
4949
```ts
5050
import { backward, forward } from '@js-entity-repos/core/dist/types/PaginationDirection';
5151
import { asc, desc } from '@js-entity-repos/core/dist/types/SortOrder';
52+
import { start } from '@js-entity-repos/core/dist/types/Cursor';
5253

53-
const { entities, nextCursor, previousCursor } = await facade.getEntities({
54+
const firstForwardPage = await facade.getEntities({
5455
filter: { foo: 'demo' },
5556
sort: { id: asc, bar: desc },
56-
pagination: { limit: 10, direction: forward, cursor: undefined },
57-
});
58-
const secondPage = await facade.getEntities({
59-
filter: { foo: 'demo' },
60-
sort: { id: asc, bar: desc },
61-
pagination: { limit: 10, direction: forward, cursor: nextCursor },
62-
});
63-
const firstPage = await facade.getEntities({
64-
filter: { foo: 'demo' },
65-
sort: { id: asc, bar: desc },
66-
pagination: { limit: 10, direction: backward, cursor: secondPage.previousCursor },
57+
pagination: { limit: 10, direction: forward, cursor: start },
6758
});
59+
const firstPageEntities = firstForwardPage.entities;
60+
if (firstForwardPage.hasMoreForward) {
61+
const secondForwardPage = await facade.getEntities({
62+
filter: { foo: 'demo' },
63+
sort: { id: asc, bar: desc },
64+
pagination: { limit: 10, direction: forward, cursor: firstForwardPage.forwardCursor },
65+
});
66+
const secondPageEntities = secondForwardPage.entities;
67+
if (secondForwardPage.hasMoreBackward) {
68+
const firstPage = await facade.getEntities({
69+
filter: { foo: 'demo' },
70+
sort: { id: asc, bar: desc },
71+
pagination: { limit: 10, direction: backward, cursor: secondForwardPage.backwardCursor },
72+
});
73+
}
74+
}
6875
```
6976

7077
This package contains the [get entities tests](../src/tests/getEntities) and the [get entities signature](../src/signatures/GetEntities.ts) for this function.

Diff for: docs/utils.md

+11-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This package contains some common utility functions that can be used by both use
44

55
- [convertPropertyFilter](#convertpropertyfilter)
66
- [createCursorFromEntity](#createcursorfromentity)
7+
- [createCursorsFromEntities](#createcursorsfromentities)
8+
- [createGetEntitiesResult](#creategetentitiesresult)
79
- [createPaginationFilter](#createpaginationfilter)
810

911
### convertPropertyFilter
@@ -21,48 +23,37 @@ convertPropertyFilter({
2123
```
2224

2325
### 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)..
26+
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 the [`createCursorsFromEntities` util](#createcursorsfromentities). Check out the [`createCursorsFromEntities` implementation](../src/utils/createCursorsFromEntities/index.ts) for an example of its use.
2527

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
28+
### createCursorsFromEntities
29+
Creates a `forwardCursor` and a `backwardCursor` from an array of entities along with a sort and cursor. This function is usually used by the [`createGetEntitiesResult` util](#creategetentitiesresult). Check out the [`createGetEntitiesResult` implementation](../src/utils/createGetEntitiesResult/index.ts) for an example of its use.
3230

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-
```
31+
### createGetEntitiesResult
32+
This function is usually used by [concrete implementations of the Facade](./facade.md#facade) to construct the result for a request to get entities. Check out [Knex's getEntities implementation](https://github.com/js-entity-repos/knex/blob/master/src/functions/getEntities.ts) for an example of its use.
4333

4434
### createPaginationFilter
4535
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).
4636

4737
```ts
4838
import createPaginationFilter from '@js-entity-repos/core/dist/utils/createPaginationFilter';
39+
import { start } from '@js-entity-repos/core/dist/types/Cursor';
4940
import { asc, desc } from '@js-entity-repos/core/dist/types/SortOrder';
5041
import { backward, forward } from '@js-entity-repos/core/dist/types/PaginationDirection';
5142

5243
createPaginationFilter(
53-
{ cursor: undefined, direction: forward, limit: 1 },
44+
{ cursor: start, direction: forward, limit: 1 },
5445
{ id: asc, numberProp: desc }
5546
);
5647
// Returns {}
5748

5849
createPaginationFilter(
59-
{ cursor: nextCursor, direction: forward, limit: 1 },
50+
{ cursor: forwardCursor, direction: forward, limit: 1 },
6051
{ id: asc, numberProp: desc }
6152
);
6253
// Returns the result of { id: { $gt: lastEntity.id }, numberProp: { $lte: lastEntity.numberProp } }
6354

6455
createPaginationFilter(
65-
{ cursor: prevCursor, direction: backward, limit: 1 },
56+
{ cursor: backwardCursor, direction: backward, limit: 1 },
6657
{ id: asc, numberProp: desc }
6758
);
6859
// Returns the result of { id: { $lt: firstEntity.id }, numberProp: { $gte: firstEntity.numberProp } }

Diff for: src/errors/PaginationFilterError.ts

-8
This file was deleted.

Diff for: src/signatures/GetEntities.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ export interface Opts<E extends Entity> {
1212

1313
export interface Result<E extends Entity> {
1414
readonly entities: E[];
15-
readonly nextCursor: Cursor;
16-
readonly previousCursor: Cursor;
15+
readonly forwardCursor: Cursor;
16+
readonly backwardCursor: Cursor;
17+
readonly hasMoreForward: boolean;
18+
readonly hasMoreBackward: boolean;
1719
}
1820

1921
export type Signature<E extends Entity> = (opts: Opts<E>) => Promise<Result<E>>;

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

+65-41
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
// tslint:disable:max-file-line-count
12
import 'mocha'; // tslint:disable-line:no-import-side-effect
23
import * as assert from 'power-assert';
34
import Facade from '../../Facade';
4-
import Cursor, { end, start } from '../../types/Cursor';
5+
import Cursor, { start } from '../../types/Cursor';
56
import Pagination from '../../types/Pagination';
67
import PaginationDirection, { backward, forward } from '../../types/PaginationDirection';
78
import Sort from '../../types/Sort';
@@ -10,77 +11,100 @@ import createCursorFromEntity from '../../utils/createCursorFromEntity';
1011
import { TestEntity, testEntity } from '../utils/testEntity';
1112

1213
export default (facade: Facade<TestEntity>) => {
14+
const sort: Sort<TestEntity> = { id: asc };
1315
const firstId = 'test_id_1';
1416
const secondId = 'test_id_2';
1517
const firstEntity = { ...testEntity, id: firstId };
1618
const secondEntity = { ...testEntity, id: secondId };
17-
const sort: Sort<TestEntity> = { id: asc };
19+
const firstCursor = createCursorFromEntity(firstEntity, sort);
20+
const secondCursor = createCursorFromEntity(secondEntity, sort);
1821

1922
const createTestEntities = async () => {
2023
await facade.createEntity({ id: firstId, entity: firstEntity });
2124
await facade.createEntity({ id: secondId, entity: secondEntity });
2225
};
2326

24-
const paginate = (cursor: Cursor, direction: PaginationDirection) => {
27+
const paginate = async (cursor: Cursor, direction: PaginationDirection) => {
2528
const pagination: Pagination = { cursor, direction, limit: 1 };
29+
await createTestEntities();
2630
return facade.getEntities({ pagination, sort });
2731
};
2832

2933
it('should return all entities when pagination is not defined', async () => {
3034
await createTestEntities();
3135
const result = await facade.getEntities({});
32-
assert.deepEqual(result.entities, [firstEntity, secondEntity]);
33-
assert.equal(result.previousCursor, createCursorFromEntity(firstEntity, sort));
34-
assert.equal(result.nextCursor, end);
36+
assert.deepEqual(result, {
37+
backwardCursor: firstCursor,
38+
entities: [firstEntity, secondEntity],
39+
forwardCursor: secondCursor,
40+
hasMoreBackward: false,
41+
hasMoreForward: false,
42+
});
3543
});
3644

3745
it('should return first entity when paginating forward with start cursor', async () => {
38-
await createTestEntities();
39-
const finalResult = await paginate(start, forward);
40-
assert.deepEqual(finalResult.entities, [firstEntity]);
41-
assert.equal(finalResult.previousCursor, end);
42-
assert.equal(finalResult.nextCursor, createCursorFromEntity(firstEntity, sort));
46+
const result = await paginate(start, forward);
47+
assert.deepEqual(result, {
48+
backwardCursor: firstCursor,
49+
entities: [firstEntity],
50+
forwardCursor: firstCursor,
51+
hasMoreBackward: false,
52+
hasMoreForward: true,
53+
});
4354
});
4455

4556
it('should return second entity when paginating forward with first cursor', async () => {
46-
await createTestEntities();
47-
const firstResult = await paginate(start, forward);
48-
const finalResult = await paginate(firstResult.nextCursor, forward);
49-
assert.deepEqual(finalResult.entities, [secondEntity]);
50-
assert.equal(finalResult.previousCursor, createCursorFromEntity(secondEntity, sort));
51-
assert.equal(finalResult.nextCursor, end);
57+
const result = await paginate(firstCursor, forward);
58+
assert.deepEqual(result, {
59+
backwardCursor: secondCursor,
60+
entities: [secondEntity],
61+
forwardCursor: secondCursor,
62+
hasMoreBackward: true,
63+
hasMoreForward: false,
64+
});
5265
});
5366

54-
it('should return no entities when paginating forward with end cursor', async () => {
55-
await createTestEntities();
56-
const finalResult = await paginate(end, forward);
57-
assert.deepEqual(finalResult.entities, []);
58-
assert.equal(finalResult.previousCursor, start);
59-
assert.equal(finalResult.nextCursor, end);
67+
it('should return no entities when paginating forward with second cursor', async () => {
68+
const result = await paginate(secondCursor, forward);
69+
assert.deepEqual(result, {
70+
backwardCursor: secondCursor,
71+
entities: [],
72+
forwardCursor: secondCursor,
73+
hasMoreBackward: true,
74+
hasMoreForward: false,
75+
});
6076
});
6177

6278
it('should return second entity when paginating backward with start cursor', async () => {
63-
await createTestEntities();
64-
const finalResult = await paginate(start, backward);
65-
assert.deepEqual(finalResult.entities, [secondEntity]);
66-
assert.equal(finalResult.previousCursor, createCursorFromEntity(secondEntity, sort));
67-
assert.equal(finalResult.nextCursor, end);
79+
const result = await paginate(start, backward);
80+
assert.deepEqual(result, {
81+
backwardCursor: secondCursor,
82+
entities: [secondEntity],
83+
forwardCursor: secondCursor,
84+
hasMoreBackward: true,
85+
hasMoreForward: false,
86+
});
6887
});
6988

70-
it('should return first entity when paginating backward with first cursor', async () => {
71-
await createTestEntities();
72-
const firstResult = await paginate(start, backward);
73-
const finalResult = await paginate(firstResult.previousCursor, backward);
74-
assert.deepEqual(finalResult.entities, [firstEntity]);
75-
assert.equal(finalResult.previousCursor, end);
76-
assert.equal(finalResult.nextCursor, createCursorFromEntity(firstEntity, sort));
89+
it('should return first entity when paginating backward with second cursor', async () => {
90+
const result = await paginate(secondCursor, backward);
91+
assert.deepEqual(result, {
92+
backwardCursor: firstCursor,
93+
entities: [firstEntity],
94+
forwardCursor: firstCursor,
95+
hasMoreBackward: false,
96+
hasMoreForward: true,
97+
});
7798
});
7899

79-
it('should return no entities when paginating backward with end cursor', async () => {
80-
await createTestEntities();
81-
const finalResult = await paginate(end, backward);
82-
assert.deepEqual(finalResult.entities, []);
83-
assert.equal(finalResult.previousCursor, end);
84-
assert.equal(finalResult.nextCursor, start);
100+
it('should return no entities when paginating backward with first cursor', async () => {
101+
const result = await paginate(firstCursor, backward);
102+
assert.deepEqual(result, {
103+
backwardCursor: firstCursor,
104+
entities: [],
105+
forwardCursor: firstCursor,
106+
hasMoreBackward: false,
107+
hasMoreForward: true,
108+
});
85109
});
86110
};

Diff for: src/types/Cursor.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export const start = undefined;
2-
export const end = '';
32

4-
type Cursor = string | typeof start | typeof end;
3+
type Cursor = string | typeof start;
54

65
export default Cursor;

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

-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
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 { end } from '../../types/Cursor';
54
import { asc } from '../../types/SortOrder';
65
import createCursorFromEntity from './index';
76

87
describe('createCursorFromEntity', () => {
9-
it('should return undefined when the entity is undefined', () => {
10-
const actualResult = createCursorFromEntity<TestEntity>(undefined, { id: asc });
11-
assert.equal(actualResult, end);
12-
});
13-
148
it('should return the correct cursor when the entity is defined', () => {
159
const actualResult = createCursorFromEntity<TestEntity>(testEntity, { id: asc });
1610
assert.equal(actualResult, 'eyJpZCI6InRlc3RfaWQifQ==');

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

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import * as btoa from 'btoa';
22
import { get, set } from 'lodash';
3-
import Cursor, { end } from '../../types/Cursor';
3+
import Cursor from '../../types/Cursor';
44
import Entity from '../../types/Entity';
55
import Sort from '../../types/Sort';
66

7-
export default <E extends Entity>(entity: E | undefined, sort: Sort<E>): Cursor => {
8-
if (entity === undefined) {
9-
return end;
10-
}
7+
export default <E extends Entity>(entity: E, sort: Sort<E>): Cursor => {
118
const sortKeys = Object.keys(sort);
129
const cursorResult = sortKeys.reduce<Partial<E>>((result, sortKey) => {
1310
return set(result, sortKey, get(entity, sortKey));

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

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'mocha'; // tslint:disable-line:no-import-side-effect
2+
import * as assert from 'power-assert';
3+
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
4+
import { start } from '../../types/Cursor';
5+
import Sort from '../../types/Sort';
6+
import { asc } from '../../types/SortOrder';
7+
import createCursorsFromEntities from './index';
8+
9+
describe('createCursorsFromEntities', () => {
10+
const sort: Sort<TestEntity> = { id: asc };
11+
const firstId = 'test_id_1';
12+
const secondId = 'test_id_2';
13+
const firstEntity = { ...testEntity, id: firstId };
14+
const secondEntity = { ...testEntity, id: secondId };
15+
const firstCursor = 'eyJpZCI6InRlc3RfaWRfMSJ9';
16+
const secondCursor = 'eyJpZCI6InRlc3RfaWRfMiJ9';
17+
18+
it('should return the correct cursors when there are no entities', () => {
19+
const entities: TestEntity[] = [];
20+
const cursor = start;
21+
const actualResult = createCursorsFromEntities({ cursor, entities, sort });
22+
assert.deepEqual(actualResult, {
23+
backwardCursor: start,
24+
forwardCursor: start,
25+
});
26+
});
27+
28+
it('should return the correct cursors when there is one entity', () => {
29+
const entities = [firstEntity];
30+
const cursor = start;
31+
const actualResult = createCursorsFromEntities({ cursor, entities, sort });
32+
assert.deepEqual(actualResult, {
33+
backwardCursor: firstCursor,
34+
forwardCursor: firstCursor,
35+
});
36+
});
37+
38+
it('should return the correct cursors when there are many entities', () => {
39+
const entities = [firstEntity, secondEntity];
40+
const cursor = start;
41+
const actualResult = createCursorsFromEntities({ cursor, entities, sort });
42+
assert.deepEqual(actualResult, {
43+
backwardCursor: firstCursor,
44+
forwardCursor: secondCursor,
45+
});
46+
});
47+
});

0 commit comments

Comments
 (0)