Skip to content

Commit

Permalink
Implement page number pagination (#3)
Browse files Browse the repository at this point in the history
* Implement page number pagination

* Add docs
  • Loading branch information
deptyped authored Jan 19, 2023
1 parent faf1235 commit c79a0ef
Show file tree
Hide file tree
Showing 16 changed files with 515 additions and 46 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ jobs:
- run: npm ci
- run: npm run lint
- run: npx prisma generate && npm run build
- run: npm test
- run: npx prisma migrate deploy && npm test

84 changes: 74 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,89 @@
# Prisma Pagination Extension

## Introduction

Prisma Client extension template with watch mode and testing.
Prisma Client extension for pagination.

## Features

## Getting Started
- [Page number pagination](#page-number-pagination).

### 1. Clone the repository or generate new repository using this template via [link](https://github.com/deptyped/prisma-extension-template/generate)
## Installation

```bash
git clone https://github.com/deptyped/prisma-extension-template
npm i prisma-extension-pagination
```

### 2. Install dependencies
## Usage

```bash
npm i
### Install extension

```ts
import { PrismaClient } from "@prisma/client";
import pagination from "prisma-extension-pagination";

const prisma = new PrismaClient().$extends(pagination);
```

### 3. Start watch mode
### Page number pagination

```bash
npm run dev
Page number pagination uses `limit` to select a limited range and `page` to load a specific page of results.

#### Load first page

```ts
const [users, meta] = prisma.user.paginate().withPages({
limit: 10
});

// meta contains the following
{
currentPage: 1,
isFirstPage: true,
isLastPage: false,
previousPage: null,
nextPage: 2,
pageCount: null,
}
```

#### Load specific page

```ts
const [users, meta] = prisma.user.paginate().withPages({
limit: 10,
page: 2
});

// meta contains the following
{
currentPage: 2,
isFirstPage: false,
isLastPage: false,
previousPage: 1,
nextPage: 3,
pageCount: null,
}
```

#### Calculate page count

```ts
const [users, meta] = prisma.user.paginate().withPages({
limit: 10,
page: 2,
includePageCount: true,
});

// meta contains the following
{
currentPage: 2,
isFirstPage: false,
isLastPage: false,
previousPage: 1,
nextPage: 3,
pageCount: 10, // the number of pages is calculated
}
```

## License
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
globalSetup: "<rootDir>/test/bootstrap/setup.ts",
globalTeardown: "<rootDir>/test/bootstrap/teardown.ts",
};
24 changes: 24 additions & 0 deletions prisma/migrations/20230118181852_initial/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT
);

-- CreateTable
CREATE TABLE "Post" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"title" TEXT
);

-- CreateTable
CREATE TABLE "PostOnUser" (
"userId" INTEGER NOT NULL,
"postId" INTEGER NOT NULL,

PRIMARY KEY ("userId", "postId"),
CONSTRAINT "PostOnUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "PostOnUser_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "PostOnUser_userId_postId_key" ON "PostOnUser"("userId", "postId");
3 changes: 3 additions & 0 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"
29 changes: 13 additions & 16 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
previewFeatures = ["clientExtensions"]
Expand All @@ -13,24 +10,24 @@ datasource db {

model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
profile Profile?
posts PostOnUser[]
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
title String?
authors PostOnUser[]
}

model Profile {
id Int @id @default(autoincrement())
bio String?
user User @relation(fields: [userId], references: [id])
userId Int @unique
model PostOnUser {
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
postId Int
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
@@id([userId, postId])
@@unique([userId, postId])
}
138 changes: 135 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,141 @@
import { Prisma } from "@prisma/client";
import { PageNumberPaginationOptions, PageNumberPaginationMeta } from "./types";

type PrismaModel = {
[k in "findMany" | "count"]: CallableFunction;
};

type PrismaQuery = {
where: Record<string, unknown>;
};

const paginateWithPages = async (
model: PrismaModel,
query: PrismaQuery,
limit: number,
currentPage: number
): Promise<[unknown, PageNumberPaginationMeta]> => {
const results = await model.findMany({
...query,
...{
skip: (currentPage - 1) * limit,
take: limit + 1,
},
});

const previousPage = currentPage > 1 ? currentPage - 1 : null;
const nextPage = results.length > limit ? currentPage + 1 : null;
if (nextPage) {
results.pop();
}

return [
results,
{
isFirstPage: previousPage === null,
isLastPage: nextPage === null,
currentPage,
previousPage,
nextPage,
pageCount: null,
},
];
};

const paginateWithPagesIncludePageCount = async (
model: PrismaModel,
query: PrismaQuery,
limit: number,
currentPage: number
): Promise<[unknown, PageNumberPaginationMeta]> => {
const [results, totalCount] = await Promise.all([
model.findMany({
...query,
...{
skip: (currentPage - 1) * limit,
take: limit,
},
}),
model.count({ where: query?.where }),
]);

const pageCount = Math.ceil(totalCount / limit);
const previousPage = currentPage > 1 ? currentPage - 1 : null;
const nextPage = currentPage < pageCount ? currentPage + 1 : null;

return [
results,
{
isFirstPage: previousPage === null,
isLastPage: nextPage === null,
currentPage,
previousPage,
nextPage,
pageCount,
},
];
};

export const extension = Prisma.defineExtension({
client: {
$deepThought() {
return 42;
name: "pagination",
model: {
$allModels: {
paginate<T extends PrismaModel, A>(
this: T,
args?: Prisma.Exact<
A,
Omit<Prisma.Args<T, "findMany">, "cursor" | "take" | "skip">
>
) {
return {
withPages: async (
options: PageNumberPaginationOptions
): Promise<
[Prisma.Result<T, A, "findMany">, PageNumberPaginationMeta]
> => {
const {
page: currentPage,
limit,
includePageCount,
} = {
page: 1,
includePageCount: false,
...options,
} satisfies PageNumberPaginationOptions;

if (
typeof currentPage !== "number" ||
currentPage < 1 ||
currentPage > Number.MAX_SAFE_INTEGER
) {
throw new Error("Invalid page option value");
}

if (
typeof limit !== "number" ||
limit < 1 ||
limit > Number.MAX_SAFE_INTEGER
) {
throw new Error("Invalid limit option value");
}

const query = (args ?? {}) as PrismaQuery;

return (
includePageCount
? paginateWithPagesIncludePageCount(
this,
query,
limit,
currentPage
)
: paginateWithPages(this, query, limit, currentPage)
) as Promise<
[Prisma.Result<T, A, "findMany">, PageNumberPaginationMeta]
>;
},
};
},
},
},
});
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { extension as default } from "./extension";
export * from "./types";
14 changes: 14 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type PageNumberPaginationOptions = {
limit: number;
page?: number;
includePageCount?: boolean;
};

export type PageNumberPaginationMeta = {
isFirstPage: boolean;
isLastPage: boolean;
currentPage: number;
previousPage: number | null;
nextPage: number | null;
pageCount: number | null;
};
36 changes: 36 additions & 0 deletions test/bootstrap/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { USERS_COUNT } from "../helpers/constants";
import { prisma } from "../helpers/prisma";

const setup = async () => {
await prisma.$connect();

await Promise.all(
[...Array(USERS_COUNT)].map(() =>
prisma.user.create({
select: {
id: true,
},
data: {
posts: {
create: [
{
post: {
create: {},
},
},
{
post: {
create: {
title: "Untitled",
},
},
},
],
},
},
})
)
);
};

export default setup;
Loading

0 comments on commit c79a0ef

Please sign in to comment.