Last active
March 7, 2020 23:44
-
-
Save Mando75/a94f295bca421aff4db34af5d234018b to your computer and use it in GitHub Desktop.
An example PaginationHelper for typeorm graphql loader
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 { FieldNode, GraphQLResolveInfo, SelectionNode } from "graphql"; | |
import { GraphQLDatabaseLoader } from "@mando75/typeorm-graphql-loader"; | |
import { SearchOptions, FeedNodeInfo } from "@mando75/typeorm-graphql-loader/dist/types"; | |
import { LoaderSearchMethod } from "@mando75/typeorm-graphql-loader/dist/base"; | |
import { OrderByCondition } from "typeorm"; | |
type getFeedOptions = { | |
search?: SearchOptions; | |
order?: OrderByCondition; | |
}; | |
/** | |
* Helper class to assist with pagination | |
*/ | |
export class PaginationHelper { | |
defaultFeedOffset: number; | |
defaultFeedLimit: number; | |
maxFeedLimit: number; | |
entityLoader: GraphQLDatabaseLoader; | |
constructor(entityLoader: GraphQLDatabaseLoader) { | |
this.maxFeedLimit = 15; | |
this.defaultFeedLimit = this.maxFeedLimit; | |
this.defaultFeedOffset = 0; | |
this.entityLoader = entityLoader; | |
} | |
public static getDefaultSearchOptions( | |
searchText: string | null | undefined, | |
columns: Array<string | Array<string>> | |
): SearchOptions | undefined { | |
if (!searchText) return undefined; | |
else { | |
return { | |
searchText, | |
searchMethod: LoaderSearchMethod.ANY_POSITION, | |
caseSensitive: false, | |
searchColumns: columns | |
}; | |
} | |
} | |
/** | |
* Validate feed query options. Will check for | |
* null or invalid options and reset to default | |
* @param offset | |
* @param limit | |
*/ | |
public validateFeedOptions(offset: number | null | undefined, limit: number | null | undefined) { | |
// default min | |
if (!offset || offset < 0) { | |
offset = this.defaultFeedOffset; | |
} | |
// default and max limit | |
if (!limit || limit < 0 || limit > this.maxFeedLimit) { | |
limit = this.defaultFeedLimit; | |
} | |
return { limit, offset }; | |
} | |
/** | |
* Helper method to get the next feed params | |
* @param pagination | |
* @param count | |
*/ | |
public getNextFeedOffset(pagination: { offset: number; limit: number }, count: number) { | |
const { offset, limit } = this.validateFeedOptions(pagination.offset, pagination.limit); | |
const nextOffset = offset + limit; | |
const recordsLeft = count - nextOffset; | |
const newOffset = recordsLeft < 1 ? count : nextOffset; | |
return { | |
offset: newOffset, | |
hasMore: newOffset !== count | |
}; | |
} | |
/** | |
* Finds a single node in the GraphQL AST to return the feed info for | |
* @param info | |
* @param fieldName | |
*/ | |
public getFeedNodeInfo(info: GraphQLResolveInfo, fieldName: string): FeedNodeInfo { | |
const childFieldNode = info.fieldNodes | |
.map(node => (node.selectionSet ? node.selectionSet.selections : [])) | |
.flat() | |
.find((selection: SelectionNode) => | |
selection.kind !== "InlineFragment" ? selection.name.value === fieldName : false | |
) as FieldNode; | |
const fieldNodes = [childFieldNode]; | |
return { fieldNodes, fragments: info.fragments, fieldName }; | |
} | |
public async getFeed<T>( | |
entity: Function, | |
feed: GQL.IFeedParams, | |
where: Partial<T>, | |
info: GraphQLResolveInfo, | |
fieldName: string, | |
options?: getFeedOptions | |
) { | |
const search = options ? options.search : undefined; | |
const order = options ? options.order : undefined; | |
const pagination = this.validateFeedOptions(feed.offset, feed.limit); | |
const [records, count] = await this.entityLoader.loadManyPaginated<T>( | |
entity, | |
where, | |
this.getFeedNodeInfo(info, fieldName), | |
pagination, | |
{ search, order } | |
); | |
const result: { [k: string]: Array<T> | number | boolean } = { | |
...this.getNextFeedOffset(pagination, count) | |
}; | |
result[fieldName] = records; | |
return result; | |
} | |
} |
Basically, you are able to pass the name of the field you are paginating to the helper and it will extract the child ResolveInfo nodes for you.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage:
GraphQL Types
Example resolver