From edbfa64a0a268a7ba9b66b54f8cee143c26ce5f6 Mon Sep 17 00:00:00 2001 From: BenBenHu Date: Wed, 4 Mar 2020 13:40:19 +0800 Subject: [PATCH] feat: return cursor and entities with single paginator.paginate function call --- README.md | 12 +++++++----- src/Paginator.ts | 32 ++++++++++++++++++++---------- src/index.ts | 4 ++-- test/integration.ts | 47 +++++++++++++++++++++++++++------------------ 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 2a20bc9..a82d9e0 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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 { + data: Entity[]; + cursor: Cursor; +} + interface Cursor { beforeCursor: string | null; afterCursor: string | null; diff --git a/src/Paginator.ts b/src/Paginator.ts index 9f93baf..5449600 100644 --- a/src/Paginator.ts +++ b/src/Paginator.ts @@ -25,6 +25,11 @@ export interface Cursor { afterCursor: string | null; } +export interface PagingResult { + data: Entity[]; + cursor: Cursor; +} + export default class Paginator { private afterCursor: string | null = null; @@ -65,14 +70,7 @@ export default class Paginator { this.order = order; } - public getCursor(): Cursor { - return { - afterCursor: this.nextAfterCursor, - beforeCursor: this.nextBeforeCursor, - }; - } - - public async paginate(builder: SelectQueryBuilder): Promise { + public async paginate(builder: SelectQueryBuilder): Promise> { const entities = await this.appendPagingQuery(builder).getMany(); const hasMore = entities.length > this.limit; @@ -81,7 +79,7 @@ export default class Paginator { } if (entities.length === 0) { - return entities; + return this.toPagingResult(entities); } if (!this.hasAfterCursor() && this.hasBeforeCursor()) { @@ -96,7 +94,14 @@ export default class Paginator { 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): SelectQueryBuilder { @@ -196,4 +201,11 @@ export default class Paginator { ? 'DESC' : 'ASC'; } + + private toPagingResult(entities: Entity[]): PagingResult { + return { + data: entities, + cursor: this.getCursor(), + }; + } } diff --git a/src/index.ts b/src/index.ts index cb87a7a..60ca35a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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; diff --git a/test/integration.ts b/test/integration.ts index 8ba9d45..cbfe67e 100644 --- a/test/integration.ts +++ b/test/integration.ts @@ -2,7 +2,7 @@ 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', () => { @@ -10,7 +10,7 @@ describe('TypeORM cursor-based pagination test', () => { await createConnection({ type: 'postgres', host: 'localhost', - port: 5432, + port: 5433, username: 'test', password: 'test', database: 'test', @@ -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; + let nextPageResult: PagingResult; it('should have afterCursor if the result set has next page', async () => { const queryBuilder = createQueryBuilder(); @@ -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 () => { @@ -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 () => { @@ -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 () => { @@ -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 () => { @@ -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 () => {