Last active
December 21, 2022 17:19
Revisions
-
VojtaSim revised this gist
Apr 26, 2019 . 1 changed file with 28 additions and 54 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -18,6 +18,7 @@ export class CursorPaginationArgs { export class CursorPagination<TEntity> { protected resultsQuery: SelectQueryBuilder<TEntity>; protected countQuery: SelectQueryBuilder<TEntity>; protected args: CursorPaginationArgs; @@ -44,13 +45,8 @@ export class CursorPagination<TEntity> { selectiveCondition = [`${this.tableName}.${this.cursorColumn} < :cursor`, { cursor: args.before }]; } this.countQuery = query.clone(); this.resultsQuery = this.applyWhereConditionToQuery(query, selectiveCondition) .orderBy(`${this.tableName}.${this.cursorColumn}`, 'ASC') .limit(args.limit); } @@ -79,21 +75,24 @@ export class CursorPagination<TEntity> { } protected async getCount(startCursor: number, endCursor: number) { const totalCountQuery = this.stipLimitationsFromQuery(this.countQuery); const beforeCountQuery = totalCountQuery.clone() .select(`COUNT(DISTINCT(${this.tableName}.${this.cursorColumn})) as \"count\"`); const afterCountQuery = beforeCountQuery.clone(); const beforeCountResult = await (this.applyWhereConditionToQuery( beforeCountQuery, [`${this.tableName}.${this.cursorColumn} < :cursor`, { cursor: startCursor }] ).getRawOne()); const afterCountResult = await (this.applyWhereConditionToQuery( afterCountQuery, [`${this.tableName}.${this.cursorColumn} > :cursor`, { cursor: endCursor }] ).getRawOne()); return { totalCount: await totalCountQuery.getCount(), moreAfter: afterCountResult['count'], moreBefore: beforeCountResult['count'] }; @@ -106,9 +105,20 @@ export class CursorPagination<TEntity> { })); } protected applyWhereConditionToQuery( query: SelectQueryBuilder<TEntity>, condition: [string, Object?] ) { if (query.expressionMap.wheres && query.expressionMap.wheres.length) { query = query.andWhere(...condition); } else { query = query.where(...condition); } return query; } protected stipLimitationsFromQuery(query: SelectQueryBuilder<TEntity>) { query.expressionMap.groupBys = []; query.expressionMap.offset = undefined; query.expressionMap.limit = undefined; @@ -117,40 +127,4 @@ export class CursorPagination<TEntity> { return query; } } -
VojtaSim created this gist
Apr 26, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,156 @@ import { ObjectType, Field, ClassType, Int, ArgsType } from 'type-graphql'; import { SelectQueryBuilder } from 'typeorm'; import Cursor, { TCursor } from 'scalar/cursor'; @ArgsType() export class CursorPaginationArgs { @Field({ nullable: true }) after?: TCursor; @Field({ nullable: true }) before?: TCursor; @Field(type => Int) limit?: number = 10; } export class CursorPagination<TEntity> { protected resultsQuery: SelectQueryBuilder<TEntity>; protected args: CursorPaginationArgs; protected tableName: string; protected cursorColumn: string; protected results: TEntity[]; constructor( query: SelectQueryBuilder<TEntity>, args: CursorPaginationArgs, tableName?: string, cursorColumn?: string ) { this.tableName = query.escape(tableName); this.cursorColumn = query.escape(cursorColumn); this.args = args; let selectiveCondition: [string, Object?] = [`${this.cursorColumn} >= 0`]; if (args.after) { selectiveCondition = [`${this.tableName}.${this.cursorColumn} > :cursor`, { cursor: args.after }]; } else if (args.before) { selectiveCondition = [`${this.tableName}.${this.cursorColumn} < :cursor`, { cursor: args.before }]; } if (query.expressionMap.wheres && query.expressionMap.wheres.length) { query = query.andWhere(...selectiveCondition); } else { query = query.where(...selectiveCondition); } this.resultsQuery = query .orderBy(`${this.tableName}.${this.cursorColumn}`, 'ASC') .limit(args.limit); } public async buildResponse(): Promise<any> { const results = await this.getResults(); const edges = this.createEdges(results); const startCursor = edges[0].cursor; const endCursor = edges[edges.length - 1].cursor; return { edges: edges, startCursor, endCursor, ...this.getCount(startCursor, endCursor) }; } public async getResults(): Promise<TEntity[]> { if (!this.results) { this.results = (await this.resultsQuery.getMany()); } return this.results; } protected async getCount(startCursor: number, endCursor: number) { const beforeCountQuery = this.stipLimitationsFromQuery(this.resultsQuery) .select(`COUNT(DISTINCT(${this.tableName}.${this.cursorColumn})) as \"count\"`); const afterCountQuery = beforeCountQuery.clone(); const beforeCountResult = await beforeCountQuery .where(`${this.tableName}.${this.cursorColumn} < :cursor`, { cursor: startCursor }) .getRawOne(); const afterCountResult = await afterCountQuery .where(`${this.tableName}.${this.cursorColumn} > :cursor`, { cursor: endCursor }) .getRawOne(); return { totalCount: await this.resultsQuery.getCount(), moreAfter: afterCountResult['count'], moreBefore: beforeCountResult['count'] }; } protected createEdges(results: TEntity[]) { return results.map((result: TEntity) => ({ node: result, cursor: result[this.cursorColumn] })); } protected stipLimitationsFromQuery(query: SelectQueryBuilder<TEntity>) { query = query.clone(); query.expressionMap.groupBys = []; query.expressionMap.offset = undefined; query.expressionMap.limit = undefined; query.expressionMap.skip = undefined; query.expressionMap.take = undefined; return query; } } export function CursorPaginated<TItem>(TItemClass: ClassType<TItem>) { @ObjectType(`${TItemClass.name}Edge`) class Edge { @Field(type => [TItemClass]) node!: TItem; @Field({ description: 'Cursor for single node.' }) cursor: TCursor; } @ObjectType(`${TItemClass.name}List`) class List { @Field(type => [Edge]) edges!: Edge[]; @Field(type => Cursor) startCursor?: TCursor; @Field(type => Cursor) endCursor?: TCursor; @Field(type => Int) totalCount: number; @Field() moreBefore!: number; @Field() moreAfter!: number; } return List; }