Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TypeError When Using @Paginate() with @MessagePattern() (@nestjs/microservices) #894

Merged
merged 11 commits into from
Sep 6, 2024
4 changes: 3 additions & 1 deletion src/decorator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants'
import { HttpArgumentsHost, CustomParamFactory, ExecutionContext } from '@nestjs/common/interfaces'
import { CustomParamFactory, ExecutionContext, HttpArgumentsHost } from '@nestjs/common/interfaces'
import { Request as ExpressRequest } from 'express'
import { FastifyRequest } from 'fastify'
import { Paginate, PaginateQuery } from './decorator'
Expand All @@ -18,6 +18,7 @@ const decoratorfactory = getParamDecoratorFactory<PaginateQuery>(Paginate)

function expressContextFactory(query: ExpressRequest['query']): Partial<ExecutionContext> {
const mockContext: Partial<ExecutionContext> = {
getType: <ContextType>() => 'http' as ContextType,
switchToHttp: (): HttpArgumentsHost =>
Object({
getRequest: (): Partial<ExpressRequest> =>
Expand All @@ -34,6 +35,7 @@ function expressContextFactory(query: ExpressRequest['query']): Partial<Executio

function fastifyContextFactory(query: FastifyRequest['query']): Partial<ExecutionContext> {
const mockContext: Partial<ExecutionContext> = {
getType: <ContextType>() => 'http' as ContextType,
switchToHttp: (): HttpArgumentsHost =>
Object({
getRequest: (): Partial<FastifyRequest> =>
Expand Down
38 changes: 27 additions & 11 deletions src/decorator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
import type { Request as ExpressRequest } from 'express'
import type { FastifyRequest } from 'fastify'
import { pickBy, Dictionary, isString, mapKeys } from 'lodash'
import { Dictionary, isString, mapKeys, pickBy } from 'lodash'

function isRecord(data: unknown): data is Record<string, unknown> {
return data !== null && typeof data === 'object' && !Array.isArray(data)
Expand Down Expand Up @@ -50,18 +50,34 @@ function parseParam<T>(queryParam: unknown, parserLogic: (param: string, res: an
}

export const Paginate = createParamDecorator((_data: unknown, ctx: ExecutionContext): PaginateQuery => {
const request: ExpressRequest | FastifyRequest = ctx.switchToHttp().getRequest()
const query = request.query as Record<string, unknown>
let path: string
let query: Record<string, unknown>

// Determine if Express or Fastify to rebuild the original url and reduce down to protocol, host and base url
let originalUrl: string
if (isExpressRequest(request)) {
originalUrl = request.protocol + '://' + request.get('host') + request.originalUrl
} else {
originalUrl = request.protocol + '://' + request.hostname + request.url
switch (ctx.getType()) {
case 'http':
const request: ExpressRequest | FastifyRequest = ctx.switchToHttp().getRequest()
query = request.query as Record<string, unknown>

// Determine if Express or Fastify to rebuild the original url and reduce down to protocol, host and base url
let originalUrl: string
if (isExpressRequest(request)) {
originalUrl = request.protocol + '://' + request.get('host') + request.originalUrl
} else {
originalUrl = request.protocol + '://' + request.hostname + request.url
}

const urlParts = new URL(originalUrl)
path = urlParts.protocol + '//' + urlParts.host + urlParts.pathname
break
case 'ws':
query = ctx.switchToWs().getData()
path = null
break
case 'rpc':
query = ctx.switchToRpc().getData()
path = null
break
}
const urlParts = new URL(originalUrl)
const path = urlParts.protocol + '//' + urlParts.host + urlParts.pathname

const searchBy = parseParam<string>(query.searchBy, singleSplit)
const sortBy = parseParam<[string, string]>(query.sortBy, multipleSplit)
Expand Down
40 changes: 23 additions & 17 deletions src/paginate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,16 +355,6 @@ export async function paginate<T extends ObjectLiteral>(
items = await queryBuilder.getMany()
}

let path: string
const { queryOrigin, queryPath } = getQueryUrlComponents(query.path)
if (config.relativePath) {
path = queryPath
} else if (config.origin) {
path = config.origin + queryPath
} else {
path = queryOrigin + queryPath
}

const sortByQuery = sortBy.map((order) => `&sortBy=${order.join(':')}`).join('')
const searchQuery = query.search ? `&search=${query.search}` : ''

Expand All @@ -389,6 +379,18 @@ export async function paginate<T extends ObjectLiteral>(

const options = `&limit=${limit}${sortByQuery}${searchQuery}${searchByQuery}${selectQuery}${filterQuery}`

let path: string = null
if (query.path !== null) {
// `query.path` does not exist in RPC/WS requests and is set to null then.
const { queryOrigin, queryPath } = getQueryUrlComponents(query.path)
if (config.relativePath) {
path = queryPath
} else if (config.origin) {
path = config.origin + queryPath
} else {
path = queryOrigin + queryPath
}
}
const buildLink = (p: number): string => path + '?page=' + p + options

const totalPages = isPaginated ? Math.ceil(totalItems / limit) : 1
Expand All @@ -406,13 +408,17 @@ export async function paginate<T extends ObjectLiteral>(
select: isQuerySelected ? selectParams : undefined,
filter: query.filter,
},
links: {
first: page == 1 ? undefined : buildLink(1),
previous: page - 1 < 1 ? undefined : buildLink(page - 1),
current: buildLink(page),
next: page + 1 > totalPages ? undefined : buildLink(page + 1),
last: page == totalPages || !totalItems ? undefined : buildLink(totalPages),
},
// If there is no `path`, don't build links.
links:
path !== null
? {
first: page == 1 ? undefined : buildLink(1),
previous: page - 1 < 1 ? undefined : buildLink(page - 1),
current: buildLink(page),
next: page + 1 > totalPages ? undefined : buildLink(page + 1),
last: page == totalPages || !totalItems ? undefined : buildLink(totalPages),
}
: ({} as Paginated<T>['links']),
}

return Object.assign(new Paginated<T>(), results)
Expand Down