diff --git a/CHANGELOG.md b/CHANGELOG.md index 54f0e1c..085a74b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.8.1](https://github.com/benjamin658/typeorm-cursor-pagination/compare/v0.8.0...v0.8.1) (2022-03-21) + + +### Features + +* **paginator:** convert Order type to enum ([1f4bcb4](https://github.com/benjamin658/typeorm-cursor-pagination/commit/1f4bcb447956ca497bcbada997c722d5899e77ae)) + + +### Bug Fixes + +* **tests:** make float column test deterministic ([a021235](https://github.com/benjamin658/typeorm-cursor-pagination/commit/a021235cd95e415e2732efac8f89adf5f4258448)) + ## [0.8.0](https://github.com/benjamin658/typeorm-cursor-pagination/compare/v0.7.0...v0.8.0) (2022-03-18) ## [0.7.0](https://github.com/benjamin658/typeorm-cursor-pagination/compare/v0.6.0...v0.7.0) (2022-02-21) diff --git a/package-lock.json b/package-lock.json index 439dee6..124160b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "typeorm-cursor-pagination", - "version": "0.8.0", + "version": "0.8.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index ef9f060..1390de9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "typeorm-cursor-pagination", - "version": "0.8.0", + "version": "0.8.1", "description": "Cursor-based pagination works with TypeORM.", "main": "lib/index.js", "scripts": { diff --git a/src/Paginator.ts b/src/Paginator.ts index 5b7332f..cd0a00f 100644 --- a/src/Paginator.ts +++ b/src/Paginator.ts @@ -14,7 +14,10 @@ import { pascalToUnderscore, } from './utils'; -export type Order = 'ASC' | 'DESC'; +export enum Order { + ASC = 'ASC', + DESC = 'DESC', +} export type EscapeFn = (name: string) => string; @@ -45,13 +48,13 @@ export default class Paginator { private limit = 100; - private order: Order = 'DESC'; + private order: Order = Order.DESC; public constructor( private entity: ObjectType, private paginationKeys: Extract[], private paginationUniqueKey: Extract, - ) { } + ) {} public setAlias(alias: string): void { this.alias = alias; @@ -73,7 +76,9 @@ export default class Paginator { this.order = order; } - 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; @@ -107,7 +112,9 @@ export default class Paginator { }; } - private appendPagingQuery(builder: SelectQueryBuilder): SelectQueryBuilder { + private appendPagingQuery( + builder: SelectQueryBuilder, + ): SelectQueryBuilder { const cursors: CursorParam = {}; if (this.hasAfterCursor()) { @@ -117,7 +124,9 @@ export default class Paginator { } if (Object.keys(cursors).length > 0) { - builder.andWhere(new Brackets((where) => this.buildCursorQuery(where, cursors))); + builder.andWhere( + new Brackets((where) => this.buildCursorQuery(where, cursors)), + ); } builder.take(this.limit + 1); @@ -126,31 +135,36 @@ export default class Paginator { return builder; } - private buildCursorQuery(where: WhereExpressionBuilder, cursors: CursorParam): void { + private buildCursorQuery( + where: WhereExpressionBuilder, + cursors: CursorParam, + ): void { const operator = this.getOperator(); const params: CursorParam = {}; this.paginationKeys.forEach((key) => { params[key] = cursors[key]; - where.andWhere(new Brackets((qb) => { - const paramsHolder = { - [`${key}_1`]: params[key], - [`${key}_2`]: params[key], - }; - qb.where(`${this.alias}.${key} ${operator} :${key}_1`, paramsHolder); - if (this.paginationUniqueKey !== key) { - qb.orWhere(`${this.alias}.${key} = :${key}_2`, paramsHolder); - } - })); + where.andWhere( + new Brackets((qb) => { + const paramsHolder = { + [`${key}_1`]: params[key], + [`${key}_2`]: params[key], + }; + qb.where(`${this.alias}.${key} ${operator} :${key}_1`, paramsHolder); + if (this.paginationUniqueKey !== key) { + qb.orWhere(`${this.alias}.${key} = :${key}_2`, paramsHolder); + } + }), + ); }); } private getOperator(): string { if (this.hasAfterCursor()) { - return this.order === 'ASC' ? '>' : '<'; + return this.order === Order.ASC ? '>' : '<'; } if (this.hasBeforeCursor()) { - return this.order === 'ASC' ? '<' : '>'; + return this.order === Order.ASC ? '<' : '>'; } return '='; @@ -180,11 +194,13 @@ export default class Paginator { } private encode(entity: Entity): string { - const payload = this.paginationKeys.map((key) => { - const type = this.getEntityPropertyType(key); - const value = encodeByType(type, entity[key]); - return `${key}:${value}`; - }).join(','); + const payload = this.paginationKeys + .map((key) => { + const type = this.getEntityPropertyType(key); + const value = encodeByType(type, entity[key]); + return `${key}:${value}`; + }) + .join(','); return btoa(payload); } @@ -203,13 +219,15 @@ export default class Paginator { } private getEntityPropertyType(key: string): string { - return Reflect.getMetadata('design:type', this.entity.prototype, key).name.toLowerCase(); + return Reflect.getMetadata( + 'design:type', + this.entity.prototype, + key, + ).name.toLowerCase(); } private flipOrder(order: Order): Order { - return order === 'ASC' - ? 'DESC' - : 'ASC'; + return order === Order.ASC ? Order.DESC : Order.ASC; } private toPagingResult(entities: Entity[]): PagingResult { diff --git a/src/buildPaginator.ts b/src/buildPaginator.ts index 5df6bf7..e10fdbc 100644 --- a/src/buildPaginator.ts +++ b/src/buildPaginator.ts @@ -6,7 +6,7 @@ export interface PagingQuery { afterCursor?: string; beforeCursor?: string; limit?: number; - order?: Order; + order?: Order | 'ASC' | 'DESC'; } export interface PaginationOptions { @@ -43,7 +43,7 @@ export function buildPaginator(options: PaginationOptions): Pagi } if (query.order) { - paginator.setOrder(query.order); + paginator.setOrder(query.order as Order); } return paginator; diff --git a/test/pagination.ts b/test/pagination.ts index 5f3409a..448ecc2 100644 --- a/test/pagination.ts +++ b/test/pagination.ts @@ -5,7 +5,7 @@ import { createQueryBuilder } from './utils/createQueryBuilder'; import { prepareData } from './utils/prepareData'; import { User } from './entities/User'; import { Photo } from './entities/Photo'; -import { buildPaginator } from '../src/index'; +import { buildPaginator, Order } from '../src/index'; describe('TypeORM cursor-based pagination test', () => { before(async () => { @@ -91,6 +91,8 @@ describe('TypeORM cursor-based pagination test', () => { expect(firstPageResult.data[1].id).to.not.eq(nextPageResult.data[0].id); expect(firstPageResult.data[1].balance).to.be.above(nextPageResult.data[0].balance); + expect(firstPageResult.data[0].id).to.eq(10); + expect(nextPageResult.data[0].id).to.eq(8); }); it('should return entities with given order', async () => { @@ -106,7 +108,7 @@ describe('TypeORM cursor-based pagination test', () => { entity: User, query: { limit: 1, - order: 'DESC', + order: Order.DESC, }, }); diff --git a/test/utils/prepareData.ts b/test/utils/prepareData.ts index 4c3081c..2b81dc1 100644 --- a/test/utils/prepareData.ts +++ b/test/utils/prepareData.ts @@ -9,16 +9,12 @@ function setTimestamp(i: number): Date { return now; } -function getRandomFloat(min: number, max: number): number { - const str = (Math.random() * (max - min) + min).toFixed(2); - - return parseFloat(str); -} +const balances = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2]; export async function prepareData(): Promise { const data = [...Array(10).keys()].map((i) => ({ name: `user${i}`, - balance: getRandomFloat(1, 2), + balance: balances[i], camelCaseColumn: setTimestamp(i), photos: [ {