Skip to content

Commit

Permalink
feat: implement standard offset,limit based pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Apr 2, 2020
1 parent 6ba940f commit d3584fe
Show file tree
Hide file tree
Showing 26 changed files with 1,310 additions and 139 deletions.
12 changes: 6 additions & 6 deletions .env
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
DB=pg
DB_NAME=lucid

MYSQL_HOST=mysql
MYSQL_HOST=0.0.0.0
MYSQL_PORT=3306
MYSQL_USER=virk
MYSQL_PASSWORD=password
MYSQL_USER=root
MYSQL_PASSWORD=root

PG_HOST=pg
PG_PORT=5432
PG_USER=virk
PG_HOST=0.0.0.0
PG_PORT=5522
PG_USER=relay
PG_PASSWORD=password

MSSQL_SERVER=mssql
Expand Down
13 changes: 11 additions & 2 deletions adonis-typings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare module '@ioc:Adonis/Lucid/Model' {
StrictValues,
QueryCallback,
ChainableContract,
SimplePaginatorContract,
ExcutableQueryBuilderContract,
} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

Expand Down Expand Up @@ -183,7 +184,6 @@ declare module '@ioc:Adonis/Lucid/Model' {
* Reference to query client used for making queries
*/
client: QueryClientContract
knexQuery: knex.QueryBuilder

/**
* A custom set of sideloaded properties defined on the query
Expand All @@ -202,13 +202,22 @@ declare module '@ioc:Adonis/Lucid/Model' {
*/
firstOrFail (): Promise<Result>

/**
* Perform delete operation
*/
del (): ModelQueryBuilderContract<Model, number>

/**
* Execute query with pagination
*/
paginate (page: number, perPage?: number): Promise<SimplePaginatorContract<Result[]>>

/**
* Mutations (update and increment can be one query aswell)
*/
update: Update<ModelQueryBuilderContract<Model, number>>
increment: Counter<ModelQueryBuilderContract<Model, number>>
decrement: Counter<ModelQueryBuilderContract<Model, number>>
del (): ModelQueryBuilderContract<Model, number>

/**
* Define relationships to be preloaded
Expand Down
54 changes: 53 additions & 1 deletion adonis-typings/querybuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ declare module '@ioc:Adonis/Lucid/DatabaseQueryBuilder' {
* methods to execute a query.
*/
export interface ChainableContract {
knexQuery: knex.QueryBuilder
hasAggregates: boolean,
hasGroupBy: boolean,
hasUnion: boolean,
keysResolver?: (columnName: string) => string,

from: SelectTable<this>
Expand Down Expand Up @@ -620,6 +624,44 @@ declare module '@ioc:Adonis/Lucid/DatabaseQueryBuilder' {
toKnex (client: knex.Client): knex.Raw
}

/**
* Paginator Metadata
*/
export type SimplePaginatorMeta = {
total: number,
per_page: number,
current_page: number,
last_page: number,
first_page: number,
first_page_url: string,
last_page_url: string,
next_page_url: string | null,
previous_page_url: string | null,
}

/**
* Shape of the simple paginator that works with offset and limit
*/
export interface SimplePaginatorContract<Result extends any[]> {
all (): Result
readonly firstPage: number
readonly perPage: number
readonly currentPage: number
readonly lastPage: number
readonly hasPages: boolean
readonly hasMorePages: boolean
readonly isEmpty: boolean
readonly total: number
readonly hasTotal: boolean
baseUrl (url: string): this
queryString (values: { [key: string]: any }): this
getUrl (page: number): string
getMeta (): SimplePaginatorMeta
getNextPageUrl (): string | null
getPreviousPageUrl (): string | null
toJSON (): { meta: SimplePaginatorMeta, data: Result[] }
}

/**
* Database query builder interface. It will use the `Executable` trait
* and hence must be typed properly for that.
Expand All @@ -628,7 +670,6 @@ declare module '@ioc:Adonis/Lucid/DatabaseQueryBuilder' {
Result extends any = Dictionary<any, string>,
> extends ChainableContract, ExcutableQueryBuilderContract<Result[]> {
client: QueryClientContract,
knexQuery: knex.QueryBuilder,

/**
* Clone current query
Expand All @@ -645,6 +686,17 @@ declare module '@ioc:Adonis/Lucid/DatabaseQueryBuilder' {
*/
del (): this

/**
* A shorthand to define limit and offset based upon the
* current page
*/
forPage (page: number, perPage?: number): this

/**
* Execute query with pagination
*/
paginate (page: number, perPage?: number): Promise<SimplePaginatorContract<Result[]>>

/**
* Mutations (update and increment can be one query aswell)
*/
Expand Down
21 changes: 17 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"luxon": "^1.22.2",
"macroable": "^4.0.2",
"pluralize": "^8.0.0",
"qs": "^6.9.3",
"snake-case": "^3.0.3"
},
"peerDependencies": {
Expand All @@ -66,6 +67,7 @@
"@types/luxon": "^1.22.0",
"@types/node": "^13.9.8",
"@types/pluralize": "0.0.29",
"chance": "^1.1.4",
"clone": "^2.1.2",
"commitizen": "^4.0.3",
"copyfiles": "^2.2.0",
Expand Down
16 changes: 16 additions & 0 deletions serve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { setup, cleanup } from './test-helpers'

setup(false).then(() => {
console.log('Tables created')
console.log('They will be removed upon existing this script gracefully.')
process.on('SIGTERM', async () => {
await cleanup()
console.log('Dropping tables')
process.exit(1)
})
process.on('SIGINT', async () => {
await cleanup()
console.log('Dropping tables')
process.exit(1)
})
}).catch(console.log)
152 changes: 152 additions & 0 deletions src/Database/Paginator/SimplePaginator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { stringify } from 'qs'
import {
SimplePaginatorMeta,
SimplePaginatorContract,
} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

/**
* Simple paginator works with the data set provided by the standard
* `offset` and `limit` based pagination.
*/
export class SimplePaginator implements SimplePaginatorContract<any[]> {
private qs: { [key: string]: any } = {}
private url: string = '/'

/**
* The first page is always 1
*/
public readonly firstPage: number = 1

/**
* Find if results set is empty or not
*/
public readonly isEmpty: boolean = this.rows.length === 0

/**
* Casting `total` to a number. Later, we can think of situations
* to cast it to a bigint
*/
public readonly total = Number(this.totalNumber)

/**
* Find if there are total records or not. This is not same as
* `isEmpty`.
*
* The `isEmpty` reports about the current set of results. However `hasTotal`
* reports about the total number of records, regardless of the current.
*/
public readonly hasTotal: boolean = this.total > 0

/**
* The Last page number
*/
public readonly lastPage: number = Math.max(Math.ceil(this.total / this.perPage), 1)

/**
* Find if there are more pages to come
*/
public readonly hasMorePages: boolean = this.lastPage > this.currentPage

/**
* Find if there are enough results to be paginated or not
*/
public readonly hasPages: boolean = this.currentPage !== 1 || this.hasMorePages

constructor (
private rows: any[],
private totalNumber: number,
public readonly perPage: number,
public readonly currentPage: number,
) {
}

/**
* A reference to the result rows
*/
public all () {
return this.rows
}

/**
* Returns JSON meta data
*/
public getMeta (): SimplePaginatorMeta {
return {
total: this.total,
per_page: this.perPage,
current_page: this.currentPage,
last_page: this.lastPage,
first_page: this.firstPage,
first_page_url: this.getUrl(1),
last_page_url: this.getUrl(this.lastPage),
next_page_url: this.getNextPageUrl(),
previous_page_url: this.getPreviousPageUrl(),
}
}

/**
* Returns JSON representation of the paginated
* data
*/
public toJSON () {
return {
meta: this.getMeta(),
data: this.all(),
}
}

/**
* Define query string to be appended to the pagination links
*/
public queryString (values: { [key: string]: any }): this {
this.qs = values
return this
}

/**
* Define base url for making the pagination links
*/
public baseUrl (url: string): this {
this.url = url
return this
}

/**
* Returns url for a given page. Doesn't validates the integrity of the
* page
*/
public getUrl (page: number): string {
const qs = stringify(Object.assign({}, this.qs, { page: page < 1 ? 1 : page }))
return `${this.url}?${qs}`
}

/**
* Returns url for the next page
*/
public getNextPageUrl (): string | null {
if (this.hasMorePages) {
return this.getUrl(this.currentPage + 1)
}
return null
}

/**
* Returns URL for the previous page
*/
public getPreviousPageUrl (): string | null {
if (this.currentPage > 1) {
return this.getUrl(this.currentPage - 1)
}

return null
}
}
Loading

0 comments on commit d3584fe

Please sign in to comment.