Skip to content

Instantly share code, notes, and snippets.

@Mando75
Last active March 7, 2020 23:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Mando75/a94f295bca421aff4db34af5d234018b to your computer and use it in GitHub Desktop.
Save Mando75/a94f295bca421aff4db34af5d234018b to your computer and use it in GitHub Desktop.
An example PaginationHelper for typeorm graphql loader
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;
}
}
@Mando75
Copy link
Author

Mando75 commented Jan 28, 2020

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