Skip to content

Commit

Permalink
feat(pagination): configurable pagination, prisma option (#709)
Browse files Browse the repository at this point in the history
Co-authored-by: Jason Kuhrt <jasonkuhrt@me.com>
  • Loading branch information
Gomah and jasonkuhrt authored Jun 15, 2020
1 parent 81a53b7 commit 18e017d
Show file tree
Hide file tree
Showing 9 changed files with 948 additions and 45 deletions.
26 changes: 21 additions & 5 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
OperationName,
} from './naming-strategies'
import { transformNullsToUndefined } from './null'
import { PaginationStrategy, relayLikePaginationStrategy } from './pagination'
import { paginationStrategies, PaginationStrategyTypes } from './pagination'
import { proxifyModelFunction, proxifyPublishers } from './proxifier'
import { Publisher } from './publisher'
import * as Typegen from './typegen'
Expand Down Expand Up @@ -126,6 +126,16 @@ export interface Options {
*/
typegen?: string
}
/**
* Select the pagination strategy.
*
* 'prisma' strategy results in GraphQL pagination arguments mirroring those of Prisma: skip, cursor, take
*
* 'relay' strategy results in GraphQL pagination arguments matching those of the [GraphQL Relay specification](https://relay.dev/graphql/connections.htm): before, after, first, last.
*
* @default 'relay'
*/
paginationStrategy?: 'relay' | 'prisma'
/**
* Enable experimental CRUD capabilities.
* Add a `t.crud` method in your definition block to generate CRUD resolvers in your `Query` and `Mutation` GraphQL Object Type.
Expand Down Expand Up @@ -188,6 +198,7 @@ const shouldGenerateArtifacts =
const defaultOptions = {
shouldGenerateArtifacts,
prismaClient: (ctx: any) => ctx.prisma,
paginationStrategy: 'relay' as const,
inputs: {
prismaClient: defaultClientPath,
},
Expand All @@ -206,7 +217,7 @@ export class SchemaBuilder {
readonly dmmf: DmmfDocument
protected argsNamingStrategy: ArgsNamingStrategy
protected fieldNamingStrategy: FieldNamingStrategy
protected paginationStrategy: PaginationStrategy
protected paginationStrategy: PaginationStrategyTypes
protected getPrismaClient: PrismaClientFetcher
protected publisher: Publisher
protected globallyComputedInputs: GlobalComputedInputs
Expand All @@ -222,7 +233,7 @@ export class SchemaBuilder {
}
// Internally rename the 'computedInputs' plugin option to clarify scope
this.globallyComputedInputs = config.computedInputs ? config.computedInputs : {}
this.paginationStrategy = relayLikePaginationStrategy
this.paginationStrategy = paginationStrategies[config.paginationStrategy]
this.dmmf =
options.dmmf ||
getTransformedDmmf(config.inputs.prismaClient, {
Expand Down Expand Up @@ -651,8 +662,13 @@ export class SchemaBuilder {
if (publisherConfig.pagination) {
const paginationsArgs =
publisherConfig.pagination === true
? field.args.filter((a) => this.paginationStrategy.paginationArgNames.includes(a.name))
: field.args.filter((arg) => (publisherConfig.pagination as any)[arg.name] === true)
? field.args.filter((a) => {
const argNames = this.paginationStrategy.paginationArgNames as string[]
return argNames.includes(a.name)
})
: field.args.filter((arg) => {
return (publisherConfig.pagination as any)[arg.name] === true
})

args.push(
...paginationsArgs.map((a) => {
Expand Down
6 changes: 3 additions & 3 deletions src/dmmf/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { DMMF } from '@prisma/client/runtime'
import { paginationStrategies, PaginationStrategy } from '../pagination'
import { GlobalComputedInputs, GlobalMutationResolverParams, LocalComputedInputs } from '../utils'
import { getPhotonDmmf } from './utils'
import { DmmfDocument } from './DmmfDocument'
import { DmmfTypes } from './DmmfTypes'
import { PaginationStrategy, relayLikePaginationStrategy } from '../pagination'
import { getPhotonDmmf } from './utils'

export type TransformOptions = {
globallyComputedInputs?: GlobalComputedInputs
Expand All @@ -17,7 +17,7 @@ export const getTransformedDmmf = (

const addDefaultOptions = (givenOptions?: TransformOptions): Required<TransformOptions> => ({
globallyComputedInputs: {},
paginationStrategy: relayLikePaginationStrategy,
paginationStrategy: paginationStrategies.relay,
...givenOptions,
})

Expand Down
35 changes: 35 additions & 0 deletions src/pagination/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { DMMF } from '@prisma/client/runtime'
import { DmmfTypes } from '../dmmf'
import { prismaStraegy } from './prisma'
import { relayStrategy } from './relay'

interface NormalizedPaginationArgs {
cursor?: object
skip?: string | number
take?: string | number
orderBy?: { [x: string]: 'asc' | 'desc' }
[x: string]: any
}

export interface PaginationStrategy<GraphQLPaginationArgs extends object = object> {
paginationArgNames: (keyof GraphQLPaginationArgs)[]
transformDmmfArgs(params: {
args: DmmfTypes.SchemaArg[]
paginationArgNames: string[]
field: DMMF.SchemaField
}): DmmfTypes.SchemaArg[]
resolve(args: GraphQLPaginationArgs): NormalizedPaginationArgs
}

// todo remove, but TS error if we do, dont' understand the error message
interface PaginationStrategies {
relay: typeof relayStrategy
prisma: typeof prismaStraegy
}

export const paginationStrategies: PaginationStrategies = {
relay: relayStrategy,
prisma: prismaStraegy,
}

export type PaginationStrategyTypes = typeof relayStrategy | typeof prismaStraegy
67 changes: 67 additions & 0 deletions src/pagination/prisma.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { PaginationStrategy } from '.'
import { DmmfTypes, getReturnTypeName } from '../dmmf'
import { keys } from '../utils'

interface PrismaPaginationArgs {
cursor?: object
take?: number
skip?: number
}

const prismaPaginationArgsToDmmfArgs: Record<
'take' | 'skip' | 'cursor',
(typeName: string) => DmmfTypes.SchemaArg
> = {
take: () => ({
name: 'take',
inputType: {
type: 'Int',
kind: 'scalar',
isRequired: false,
isList: false,
isNullable: false,
},
}),
skip: () => ({
name: 'skip',
inputType: {
type: 'Int',
kind: 'scalar',
isRequired: false,
isList: false,
isNullable: false,
},
}),
cursor: (typeName: string) => ({
name: 'cursor',
inputType: {
type: `${typeName}WhereUniqueInput`,
kind: 'object',
isRequired: false,
isList: false,
isNullable: false,
},
}),
}

export const prismaStraegy: PaginationStrategy<PrismaPaginationArgs> = {
paginationArgNames: keys(prismaPaginationArgsToDmmfArgs),
transformDmmfArgs({ paginationArgNames, args, field }) {
const fieldOutputTypeName = getReturnTypeName(field.outputType.type)

// Remove old pagination args
args = args.filter((dmmfArg) => !paginationArgNames.includes(dmmfArg.name))

// Push new pagination args
args.push(
prismaPaginationArgsToDmmfArgs.take(fieldOutputTypeName),
prismaPaginationArgsToDmmfArgs.skip(fieldOutputTypeName),
prismaPaginationArgsToDmmfArgs.cursor(fieldOutputTypeName)
)

return args
},
resolve(args) {
return args
},
}
51 changes: 17 additions & 34 deletions src/pagination.ts → src/pagination/relay.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import { DmmfTypes, getReturnTypeName } from './dmmf'
import { DMMF } from '@prisma/client/runtime'

interface PaginationResult {
cursor?: object
skip?: string | number
take?: string | number
orderBy?: { [x: string]: 'asc' | 'desc' }
[x: string]: any
}
import { DmmfTypes, getReturnTypeName } from '../dmmf'
import { keys } from '../utils'
import { PaginationStrategy } from './'

export interface PaginationStrategy<T = object> {
paginationArgNames: string[]
transformDmmfArgs(params: {
args: DmmfTypes.SchemaArg[]
paginationArgNames: string[]
field: DMMF.SchemaField
}): DmmfTypes.SchemaArg[]
resolve(args: T): PaginationResult
interface RelayPaginationArgs {
first?: number
last?: number
before?: object
after?: object
}

const relayLikePaginationArgs: Record<
const relayPaginationArgsToDmmfArgs: Record<
'first' | 'last' | 'before' | 'after',
(typeName: string) => DmmfTypes.SchemaArg
> = {
Expand Down Expand Up @@ -65,27 +55,20 @@ const relayLikePaginationArgs: Record<
}),
}

interface RelayLikePaginationArgs {
first?: number
last?: number
before?: object
after?: object
}

export const relayLikePaginationStrategy: PaginationStrategy<RelayLikePaginationArgs> = {
paginationArgNames: Object.keys(relayLikePaginationArgs),
transformDmmfArgs({ args, paginationArgNames, field }) {
export const relayStrategy: PaginationStrategy<RelayPaginationArgs> = {
paginationArgNames: keys(relayPaginationArgsToDmmfArgs),
transformDmmfArgs({ paginationArgNames, args, field }) {
const fieldOutputTypeName = getReturnTypeName(field.outputType.type)

// Remove old pagination args
args = args.filter((a) => !paginationArgNames.includes(a.name))
args = args.filter((dmmfArg) => !paginationArgNames.includes(dmmfArg.name))

// Push new pagination args
args.push(
relayLikePaginationArgs.first(fieldOutputTypeName),
relayLikePaginationArgs.last(fieldOutputTypeName),
relayLikePaginationArgs.before(fieldOutputTypeName),
relayLikePaginationArgs.after(fieldOutputTypeName)
relayPaginationArgsToDmmfArgs.first(fieldOutputTypeName),
relayPaginationArgsToDmmfArgs.last(fieldOutputTypeName),
relayPaginationArgsToDmmfArgs.before(fieldOutputTypeName),
relayPaginationArgsToDmmfArgs.after(fieldOutputTypeName)
)

return args
Expand Down
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,7 @@ export type LocalMutationResolverParams<MethodName extends string> = BaseMutatio
export type Context = core.GetGen<'context'>

export const isEmptyObject = (o: any) => isDeepStrictEqual(o, {})

export function keys<A extends object>(a: A): (keyof A)[] {
return Object.keys(a) as any
}
8 changes: 5 additions & 3 deletions tests/__utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import stripAnsi from 'strip-ansi'
import * as NexusPrismaBuilder from '../src/builder'
import { DmmfDocument } from '../src/dmmf'
import { transform, TransformOptions } from '../src/dmmf/transformer'
import { relayLikePaginationStrategy } from '../src/pagination'
import { paginationStrategies } from '../src/pagination'
import { render as renderTypegen } from '../src/typegen'
import { getEnginePath } from './__ensure-engine'

Expand Down Expand Up @@ -52,12 +52,14 @@ export async function generateSchemaAndTypes(
options?: TransformOptions & {
experimentalCRUD?: boolean
plugins?: Nexus.core.NexusPlugin[]
paginationType?: 'relay' | 'prisma'
}
) {
const dmmf = await getDmmf(datamodel, options)
const nexusPrisma = createNexusPrismaInternal({
dmmf,
experimentalCRUD: options?.experimentalCRUD === false ? false : true,
paginationStrategy: options?.paginationType ?? 'relay',
})
const schema = Nexus.makeSchema({
types,
Expand All @@ -71,7 +73,7 @@ export async function generateSchemaAndTypes(
typegen: renderTypegen({
dmmf,
prismaClientImportId: '@prisma/client',
paginationStrategy: relayLikePaginationStrategy,
paginationStrategy: options?.paginationStrategy ?? paginationStrategies.relay,
}),
}
}
Expand Down Expand Up @@ -99,7 +101,7 @@ export async function generateSchemaAndTypesWithoutThrowing(
const typegen = renderTypegen({
dmmf,
prismaClientImportId: '@prisma/client',
paginationStrategy: relayLikePaginationStrategy,
paginationStrategy: paginationStrategies.relay,
})

return {
Expand Down
Loading

0 comments on commit 18e017d

Please sign in to comment.