-
-
Save giddygitau/69169c4ed4008141c1434dd2ddd39054 to your computer and use it in GitHub Desktop.
TypeORM + TypeGraphQL cursor pagination
This file contains 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