Skip to content

Commit edbfa64

Browse files
committed
feat: return cursor and entities with single paginator.paginate function call
1 parent fa267c4 commit edbfa64

File tree

4 files changed

+59
-36
lines changed

4 files changed

+59
-36
lines changed

README.md

+7-5
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ const paginator = buildPaginator({
3333
});
3434

3535
// Pass queryBuilder as parameter to get paginate result.
36-
const result: User[] = await paginator.paginate(queryBuilder);
37-
38-
// Get cursor for next iteration
39-
const cursor = paginator.getCursor();
36+
const { data, cursor } = await paginator.paginate(queryBuilder);
4037
```
4138

4239
The `buildPaginator` function has the following options:
@@ -50,9 +47,14 @@ The `buildPaginator` function has the following options:
5047
* `beforeCursor`: the before cursor.
5148
* `afterCursor`: the after cursor.
5249

53-
Cursor returns by `paginator.getCursor()` for next iteration
50+
`paginator.paginate(queryBuilder)` returns the entities and cursor for next iteration
5451

5552
```typescript
53+
interface PagingResult<Entity> {
54+
data: Entity[];
55+
cursor: Cursor;
56+
}
57+
5658
interface Cursor {
5759
beforeCursor: string | null;
5860
afterCursor: string | null;

src/Paginator.ts

+22-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export interface Cursor {
2525
afterCursor: string | null;
2626
}
2727

28+
export interface PagingResult<Entity> {
29+
data: Entity[];
30+
cursor: Cursor;
31+
}
32+
2833
export default class Paginator<Entity> {
2934
private afterCursor: string | null = null;
3035

@@ -65,14 +70,7 @@ export default class Paginator<Entity> {
6570
this.order = order;
6671
}
6772

68-
public getCursor(): Cursor {
69-
return {
70-
afterCursor: this.nextAfterCursor,
71-
beforeCursor: this.nextBeforeCursor,
72-
};
73-
}
74-
75-
public async paginate(builder: SelectQueryBuilder<Entity>): Promise<Entity[]> {
73+
public async paginate(builder: SelectQueryBuilder<Entity>): Promise<PagingResult<Entity>> {
7674
const entities = await this.appendPagingQuery(builder).getMany();
7775
const hasMore = entities.length > this.limit;
7876

@@ -81,7 +79,7 @@ export default class Paginator<Entity> {
8179
}
8280

8381
if (entities.length === 0) {
84-
return entities;
82+
return this.toPagingResult(entities);
8583
}
8684

8785
if (!this.hasAfterCursor() && this.hasBeforeCursor()) {
@@ -96,7 +94,14 @@ export default class Paginator<Entity> {
9694
this.nextBeforeCursor = this.encode(entities[0]);
9795
}
9896

99-
return entities;
97+
return this.toPagingResult(entities);
98+
}
99+
100+
private getCursor(): Cursor {
101+
return {
102+
afterCursor: this.nextAfterCursor,
103+
beforeCursor: this.nextBeforeCursor,
104+
};
100105
}
101106

102107
private appendPagingQuery(builder: SelectQueryBuilder<Entity>): SelectQueryBuilder<Entity> {
@@ -196,4 +201,11 @@ export default class Paginator<Entity> {
196201
? 'DESC'
197202
: 'ASC';
198203
}
204+
205+
private toPagingResult<Entity>(entities: Entity[]): PagingResult<Entity> {
206+
return {
207+
data: entities,
208+
cursor: this.getCursor(),
209+
};
210+
}
199211
}

src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { ObjectType } from 'typeorm';
22

3-
import Paginator, { Order, Cursor } from './Paginator';
3+
import Paginator, { Order, Cursor, PagingResult } from './Paginator';
44

5-
export { Order, Cursor };
5+
export { Order, Cursor, PagingResult };
66

77
export interface PagingQuery {
88
afterCursor?: string;

test/integration.ts

+28-19
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { expect } from 'chai';
22
import { createConnection, getConnection } from 'typeorm';
33

44
import { createQueryBuilder } from './utils/createQueryBuilder';
5-
import { buildPaginator, Cursor } from '../src/index';
5+
import { buildPaginator, PagingResult } from '../src/index';
66
import { Example } from './entities/Example';
77

88
describe('TypeORM cursor-based pagination test', () => {
99
before(async () => {
1010
await createConnection({
1111
type: 'postgres',
1212
host: 'localhost',
13-
port: 5432,
13+
port: 5433,
1414
username: 'test',
1515
password: 'test',
1616
database: 'test',
@@ -21,9 +21,8 @@ describe('TypeORM cursor-based pagination test', () => {
2121
await getConnection().query('CREATE TABLE example as SELECT generate_series(1, 10) AS id;');
2222
});
2323

24-
let firstPageResult: Example[];
25-
let nextPageResult: Example[];
26-
let cursor: Cursor;
24+
let firstPageResult: PagingResult<Example>;
25+
let nextPageResult: PagingResult<Example>;
2726

2827
it('should have afterCursor if the result set has next page', async () => {
2928
const queryBuilder = createQueryBuilder();
@@ -35,11 +34,10 @@ describe('TypeORM cursor-based pagination test', () => {
3534
});
3635

3736
firstPageResult = await paginator.paginate(queryBuilder);
38-
cursor = paginator.getCursor();
3937

40-
expect(cursor.afterCursor).to.not.eq(null);
41-
expect(cursor.beforeCursor).to.eq(null);
42-
expect(firstPageResult[0].id).to.eq(10);
38+
expect(firstPageResult.cursor.afterCursor).to.not.eq(null);
39+
expect(firstPageResult.cursor.beforeCursor).to.eq(null);
40+
expect(firstPageResult.data[0].id).to.eq(10);
4341
});
4442

4543
it('should have beforeCursor if the result set has prev page', async () => {
@@ -48,16 +46,15 @@ describe('TypeORM cursor-based pagination test', () => {
4846
entity: Example,
4947
query: {
5048
limit: 1,
51-
afterCursor: cursor.afterCursor as string,
49+
afterCursor: firstPageResult.cursor.afterCursor as string,
5250
},
5351
});
5452

5553
nextPageResult = await paginator.paginate(queryBuilder);
56-
cursor = paginator.getCursor();
5754

58-
expect(cursor.afterCursor).to.not.eq(null);
59-
expect(cursor.beforeCursor).to.not.eq(null);
60-
expect(nextPageResult[0].id).to.eq(9);
55+
expect(nextPageResult.cursor.afterCursor).to.not.eq(null);
56+
expect(nextPageResult.cursor.beforeCursor).to.not.eq(null);
57+
expect(nextPageResult.data[0].id).to.eq(9);
6158
});
6259

6360
it('should return prev page result set if beforeCursor is set', async () => {
@@ -66,13 +63,13 @@ describe('TypeORM cursor-based pagination test', () => {
6663
entity: Example,
6764
query: {
6865
limit: 1,
69-
beforeCursor: cursor.beforeCursor as string,
66+
beforeCursor: nextPageResult.cursor.beforeCursor as string,
7067
},
7168
});
7269

7370
const result = await paginator.paginate(queryBuilder);
7471

75-
expect(result[0].id).to.eq(10);
72+
expect(result.data[0].id).to.eq(10);
7673
});
7774

7875
it('should return entities with given order', async () => {
@@ -97,8 +94,8 @@ describe('TypeORM cursor-based pagination test', () => {
9794
const ascResult = await ascPaginator.paginate(ascQueryBuilder);
9895
const descResult = await descPaginator.paginate(descQueryBuilder);
9996

100-
expect(ascResult[0].id).to.eq(1);
101-
expect(descResult[0].id).to.eq(10);
97+
expect(ascResult.data[0].id).to.eq(1);
98+
expect(descResult.data[0].id).to.eq(10);
10299
});
103100

104101
it('should return entities with given limit', async () => {
@@ -112,7 +109,19 @@ describe('TypeORM cursor-based pagination test', () => {
112109

113110
const result = await paginator.paginate(queryBuilder);
114111

115-
expect(result).length(10);
112+
expect(result.data).length(10);
113+
});
114+
115+
it('should return empty array and null cursor if no data', async () => {
116+
const queryBuilder = createQueryBuilder().where('example.id > :id', { id: 10 });
117+
const paginator = buildPaginator({
118+
entity: Example,
119+
});
120+
const result = await paginator.paginate(queryBuilder);
121+
122+
expect(result.data).length(0);
123+
expect(result.cursor.beforeCursor).to.eq(null);
124+
expect(result.cursor.afterCursor).to.eq(null);
116125
});
117126

118127
after(async () => {

0 commit comments

Comments
 (0)