Skip to content

Instantly share code, notes, and snippets.

@laudaikinhdi
Forked from tumainimosha/page-info.ts
Created June 29, 2023 11:15
Show Gist options
  • Save laudaikinhdi/1b781d068753b42a5fe1fd4419bd2286 to your computer and use it in GitHub Desktop.
Save laudaikinhdi/1b781d068753b42a5fe1fd4419bd2286 to your computer and use it in GitHub Desktop.

Revisions

  1. @tumainimosha tumainimosha revised this gist Jul 22, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion post.service.ts
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import { paginate } from 'paginate';
    import { paginate } from './paginate';

    @Injectable()
    export class PostService {
  2. @tumainimosha tumainimosha revised this gist Jul 22, 2020. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions post-resolver.ts
    Original file line number Diff line number Diff line change
    @@ -5,8 +5,8 @@ import { PostService } from '../providers/post.service';
    export class PostResolver {
    constructor(private readonly postService: PostService) { }

    @Query(() => PaginatedInquiry)
    getOwnInquiries(
    @Query(() => PaginatedPost)
    getPosts(
    @Args() pagination: PaginationArgs,
    @Args() filter: PostFilter,
    ): Promise<PaginatedPost> {
  3. @tumainimosha tumainimosha created this gist Jul 22, 2020.
    17 changes: 17 additions & 0 deletions page-info.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    import { ObjectType, Field } from "@nestjs/graphql";

    @ObjectType()
    export class PageInfo {

    @Field({ nullable: true })
    startCursor: string;

    @Field({ nullable: true })
    endCursor: string;

    @Field()
    hasPreviousPage: boolean;

    @Field()
    hasNextPage: boolean;
    }
    104 changes: 104 additions & 0 deletions paginate.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    import { Logger } from '@nestjs/common';
    import { PageInfo } from './page-info';
    import { PaginationArgs } from './pagination.args';
    import { SelectQueryBuilder, MoreThan, LessThan } from 'typeorm';

    /**
    * Based on https://gist.github.com/VojtaSim/6b03466f1964a6c81a3dbf1f8cec8d5c
    */
    export async function paginate<T>(
    query: SelectQueryBuilder<T>,
    paginationArgs: PaginationArgs,
    cursorColumn = 'id',
    defaultLimit = 25,
    ): Promise<any> {

    const logger = new Logger('Pagination');

    // pagination ordering
    query.orderBy({ [cursorColumn]: 'DESC' })

    const totalCountQuery = query.clone();

    // FORWARD pagination
    if (paginationArgs.first) {

    if (paginationArgs.after) {
    const offsetId = Number(Buffer.from(paginationArgs.after, 'base64').toString('ascii'));
    logger.verbose(`Paginate AfterID: ${offsetId}`);
    query.where({ [cursorColumn]: MoreThan(offsetId) });
    }

    const limit = paginationArgs.first ?? defaultLimit;

    query.take(limit)
    }

    // REVERSE pagination
    else if (paginationArgs.last && paginationArgs.before) {
    const offsetId = Number(Buffer.from(paginationArgs.before, 'base64').toString('ascii'));
    logger.verbose(`Paginate BeforeID: ${offsetId}`);

    const limit = paginationArgs.last ?? defaultLimit;

    query
    .where({ [cursorColumn]: LessThan(offsetId) })
    .take(limit);
    }


    const result = await query.getMany();


    const startCursorId: number = result.length > 0 ? result[0][cursorColumn] : null;
    const endCursorId: number = result.length > 0 ? result.slice(-1)[0][cursorColumn] : null;


    const beforeQuery = totalCountQuery.clone();

    const afterQuery = beforeQuery.clone();

    let countBefore = 0;
    let countAfter = 0;
    if (beforeQuery.expressionMap.wheres && beforeQuery.expressionMap.wheres.length) {
    countBefore = await beforeQuery
    .andWhere(`${cursorColumn} < :cursor`, { cursor: startCursorId })
    .getCount();
    countAfter = await afterQuery
    .andWhere(`${cursorColumn} > :cursor`, { cursor: endCursorId })
    .getCount();

    } else {
    countBefore = await beforeQuery
    .where(`${cursorColumn} < :cursor`, { cursor: startCursorId })
    .getCount();

    countAfter = await afterQuery
    .where(`${cursorColumn} > :cursor`, { cursor: endCursorId })
    .getCount();

    }

    logger.debug(`CountBefore:${countBefore}`);
    logger.debug(`CountAfter:${countAfter}`);

    const edges = result.map((value) => {
    return {
    node: value,
    cursor: Buffer.from(`${value[cursorColumn]}`).toString('base64'),
    };
    });

    const pageInfo = new PageInfo();
    pageInfo.startCursor = edges.length > 0 ? edges[0].cursor : null;
    pageInfo.endCursor = edges.length > 0 ? edges.slice(-1)[0].cursor : null;

    pageInfo.hasNextPage = countAfter > 0;
    pageInfo.hasPreviousPage = countBefore > 0;
    // pageInfo.countBefore = countBefore;
    // pageInfo.countNext = countAfter;
    // pageInfo.countCurrent = edges.length;
    // pageInfo.countTotal = countAfter + countBefore + edges.length;

    return { edges, pageInfo };
    }
    9 changes: 9 additions & 0 deletions paginated-post.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    /**
    * Example of paginated graphql model
    */
    import { Post } from "../models/post.model";
    import { ObjectType } from '@nestjs/graphql';
    import { Paginated } from "src/shared/pagination/types/paginated";

    @ObjectType()
    export class PaginatedPost extends Paginated(Post) { }
    30 changes: 30 additions & 0 deletions paginated.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    import { Field, ObjectType } from '@nestjs/graphql';
    import { Type } from '@nestjs/common';
    import { PageInfo } from './page-info';


    /**
    * Based on https://docs.nestjs.com/graphql/resolvers#generics
    *
    * @param classRef
    */
    export function Paginated<T>(classRef: Type<T>): any {
    @ObjectType(`${classRef.name}Edge`, { isAbstract: true })
    abstract class EdgeType {
    @Field(() => String)
    cursor: string;

    @Field(() => classRef)
    node: T;
    }

    @ObjectType({ isAbstract: true })
    abstract class PaginatedType {
    @Field(() => [EdgeType], { nullable: true })
    edges: EdgeType[];

    @Field(() => PageInfo, { nullable: true })
    pageInfo: PageInfo;
    }
    return PaginatedType;
    }
    18 changes: 18 additions & 0 deletions pagination.args.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    import { ArgsType, Int, Field } from '@nestjs/graphql';

    @ArgsType()
    export class PaginationArgs {

    @Field(() => Int, { nullable: true })
    first: number;

    @Field(() => String, { nullable: true })
    after: string;

    @Field(() => Int, { nullable: true })
    last: number;

    @Field(() => String, { nullable: true })
    before: string;

    }
    15 changes: 15 additions & 0 deletions post-resolver.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    import { Post } from "../models/post.model";
    import { PostService } from '../providers/post.service';

    @Resolver(() => Post)
    export class PostResolver {
    constructor(private readonly postService: PostService) { }

    @Query(() => PaginatedInquiry)
    getOwnInquiries(
    @Args() pagination: PaginationArgs,
    @Args() filter: PostFilter,
    ): Promise<PaginatedPost> {
    return this.postService.getPaginatedPosts(pagination, filter);
    }
    }
    23 changes: 23 additions & 0 deletions post.service.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    import { paginate } from 'paginate';

    @Injectable()
    export class PostService {

    private readonly logger = new Logger('PostService');

    constructor(
    @InjectRepository(PostRepository)
    private postRepository: PostRepository,
    ) { }

    async getPaginatedPosts(paginationArgs: PaginationArgs, filter: PostFilter): Promise<PaginatedPost> {

    const query = await this.postRepository
    .createQueryBuilder()
    .select();

    // todo... you can apply filters here to the query as where clauses

    return paginate(query, paginationArgs);
    }
    }