Skip to content

Commit

Permalink
feat: return cursor and entities with single paginator.paginate funct…
Browse files Browse the repository at this point in the history
…ion call
  • Loading branch information
benjamin658 committed Mar 4, 2020
1 parent fa267c4 commit edbfa64
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 36 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ const paginator = buildPaginator({
});

// Pass queryBuilder as parameter to get paginate result.
const result: User[] = await paginator.paginate(queryBuilder);

// Get cursor for next iteration
const cursor = paginator.getCursor();
const { data, cursor } = await paginator.paginate(queryBuilder);
```

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

Cursor returns by `paginator.getCursor()` for next iteration
`paginator.paginate(queryBuilder)` returns the entities and cursor for next iteration

```typescript
interface PagingResult<Entity> {
data: Entity[];
cursor: Cursor;
}

interface Cursor {
beforeCursor: string | null;
afterCursor: string | null;
Expand Down
32 changes: 22 additions & 10 deletions src/Paginator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export interface Cursor {
afterCursor: string | null;
}

export interface PagingResult<Entity> {
data: Entity[];
cursor: Cursor;
}

export default class Paginator<Entity> {
private afterCursor: string | null = null;

Expand Down Expand Up @@ -65,14 +70,7 @@ export default class Paginator<Entity> {
this.order = order;
}

public getCursor(): Cursor {
return {
afterCursor: this.nextAfterCursor,
beforeCursor: this.nextBeforeCursor,
};
}

public async paginate(builder: SelectQueryBuilder<Entity>): Promise<Entity[]> {
public async paginate(builder: SelectQueryBuilder<Entity>): Promise<PagingResult<Entity>> {
const entities = await this.appendPagingQuery(builder).getMany();
const hasMore = entities.length > this.limit;

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

if (entities.length === 0) {
return entities;
return this.toPagingResult(entities);
}

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

return entities;
return this.toPagingResult(entities);
}

private getCursor(): Cursor {
return {
afterCursor: this.nextAfterCursor,
beforeCursor: this.nextBeforeCursor,
};
}

private appendPagingQuery(builder: SelectQueryBuilder<Entity>): SelectQueryBuilder<Entity> {
Expand Down Expand Up @@ -196,4 +201,11 @@ export default class Paginator<Entity> {
? 'DESC'
: 'ASC';
}

private toPagingResult<Entity>(entities: Entity[]): PagingResult<Entity> {
return {
data: entities,
cursor: this.getCursor(),
};
}
}
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ObjectType } from 'typeorm';

import Paginator, { Order, Cursor } from './Paginator';
import Paginator, { Order, Cursor, PagingResult } from './Paginator';

export { Order, Cursor };
export { Order, Cursor, PagingResult };

export interface PagingQuery {
afterCursor?: string;
Expand Down
47 changes: 28 additions & 19 deletions test/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { expect } from 'chai';
import { createConnection, getConnection } from 'typeorm';

import { createQueryBuilder } from './utils/createQueryBuilder';
import { buildPaginator, Cursor } from '../src/index';
import { buildPaginator, PagingResult } from '../src/index';
import { Example } from './entities/Example';

describe('TypeORM cursor-based pagination test', () => {
before(async () => {
await createConnection({
type: 'postgres',
host: 'localhost',
port: 5432,
port: 5433,
username: 'test',
password: 'test',
database: 'test',
Expand All @@ -21,9 +21,8 @@ describe('TypeORM cursor-based pagination test', () => {
await getConnection().query('CREATE TABLE example as SELECT generate_series(1, 10) AS id;');
});

let firstPageResult: Example[];
let nextPageResult: Example[];
let cursor: Cursor;
let firstPageResult: PagingResult<Example>;
let nextPageResult: PagingResult<Example>;

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

firstPageResult = await paginator.paginate(queryBuilder);
cursor = paginator.getCursor();

expect(cursor.afterCursor).to.not.eq(null);
expect(cursor.beforeCursor).to.eq(null);
expect(firstPageResult[0].id).to.eq(10);
expect(firstPageResult.cursor.afterCursor).to.not.eq(null);
expect(firstPageResult.cursor.beforeCursor).to.eq(null);
expect(firstPageResult.data[0].id).to.eq(10);
});

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

nextPageResult = await paginator.paginate(queryBuilder);
cursor = paginator.getCursor();

expect(cursor.afterCursor).to.not.eq(null);
expect(cursor.beforeCursor).to.not.eq(null);
expect(nextPageResult[0].id).to.eq(9);
expect(nextPageResult.cursor.afterCursor).to.not.eq(null);
expect(nextPageResult.cursor.beforeCursor).to.not.eq(null);
expect(nextPageResult.data[0].id).to.eq(9);
});

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

const result = await paginator.paginate(queryBuilder);

expect(result[0].id).to.eq(10);
expect(result.data[0].id).to.eq(10);
});

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

expect(ascResult[0].id).to.eq(1);
expect(descResult[0].id).to.eq(10);
expect(ascResult.data[0].id).to.eq(1);
expect(descResult.data[0].id).to.eq(10);
});

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

const result = await paginator.paginate(queryBuilder);

expect(result).length(10);
expect(result.data).length(10);
});

it('should return empty array and null cursor if no data', async () => {
const queryBuilder = createQueryBuilder().where('example.id > :id', { id: 10 });
const paginator = buildPaginator({
entity: Example,
});
const result = await paginator.paginate(queryBuilder);

expect(result.data).length(0);
expect(result.cursor.beforeCursor).to.eq(null);
expect(result.cursor.afterCursor).to.eq(null);
});

after(async () => {
Expand Down

0 comments on commit edbfa64

Please sign in to comment.