-
-
Save diegomais/470ebcd90bdf1dc73e5c12089935eb6d to your computer and use it in GitHub Desktop.
TypeORM + TypeGraphQL cursor pagination
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 characters
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 countQuery: 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 }]; | |
} | |
this.countQuery = query.clone(); | |
this.resultsQuery = this.applyWhereConditionToQuery(query, selectiveCondition) | |
.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 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'] | |
}; | |
} | |
protected createEdges(results: TEntity[]) { | |
return results.map((result: TEntity) => ({ | |
node: result, | |
cursor: result[this.cursorColumn] | |
})); | |
} | |
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; | |
query.expressionMap.skip = undefined; | |
query.expressionMap.take = undefined; | |
return query; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment