Skip to content

Instantly share code, notes, and snippets.

@tgriesser
Created June 20, 2019 06:14
Show Gist options
  • Save tgriesser/d098284e322807672b12125a77c96bb4 to your computer and use it in GitHub Desktop.
Save tgriesser/d098284e322807672b12125a77c96bb4 to your computer and use it in GitHub Desktop.
import { dynamicOutputMethod, core, objectType, inputObjectType } from 'nexus'
import { GraphQLFieldResolver, GraphQLResolveInfo } from 'graphql'
import _ from 'lodash'
import {
BatchResolveFunction,
createBatchResolver,
} from 'graphql-resolve-batch'
export function paginatedInputType<TypeName extends string>(
config: Omit<core.NexusInputObjectTypeConfig<TypeName>, 'definition'> & {
definition?: (t: core.InputDefinitionBlock<TypeName>) => void
perPageDefault?: number
}
) {
const { perPageDefault, ...rest } = config
return inputObjectType({
...rest,
definition(t) {
t.int('page', {
default: 1,
description: 'Which page of results we want to return, default: 1',
})
t.int('perPage', {
default: config.perPageDefault || 30,
description: 'How many results we want to return, default: 30',
})
if (config.definition) {
config.definition(t)
}
},
})
}
const basicConnectionMap = new Map<string, core.NexusObjectTypeDef<string>>()
export type InputVal<
TypeName extends string,
FieldName extends string
> = core.ArgsValue<TypeName, FieldName>['input'] & {}
export type ConnectionResult<
TypeName extends string,
FieldName extends string,
SubFieldName extends string
> =
| core.MaybePromise<core.ResultValue<TypeName, FieldName>[SubFieldName]>
| core.MaybePromiseDeep<core.ResultValue<TypeName, FieldName>[SubFieldName]>
export type BatchConnectionFieldResolver<
TypeName extends string,
FieldName extends string,
SubFieldName extends string
> = BatchResolveFunction<
core.RootValue<TypeName>,
InputVal<TypeName, FieldName>,
core.GetGen<'context'>,
ConnectionResult<TypeName, FieldName, SubFieldName>
>
export type ConnectionFieldResolver<
TypeName extends string,
FieldName extends string,
SubFieldName extends string
> = (
root: core.RootValue<TypeName>,
args: InputVal<TypeName, FieldName>,
context: core.GetGen<'context'>,
info: GraphQLResolveInfo
) => ConnectionResult<TypeName, FieldName, SubFieldName>
export type ConnectionFieldOpts<
TypeName extends string,
FieldName extends string
> = {
type:
| core.GetGen<'objectNames'>
| core.GetGen<'interfaceNames'>
| core.NexusObjectTypeDef<string>
| core.NexusInterfaceTypeDef<string>
| core.NexusWrappedType<core.AllNexusOutputTypeDefs>
filters?: Record<string, core.NexusInputFieldConfig<string, string>>
authorize?: core.AuthorizeResolver<TypeName, FieldName>
nullable?: boolean
description?: string
args?: never
list?: never
sortColumns?: Array<keyof core.RootValue<TypeName>>
} & (
| {
totalCountBatch?: never
totalCount: ConnectionFieldResolver<TypeName, FieldName, 'totalCount'>
}
| {
totalCountBatch: BatchConnectionFieldResolver<
TypeName,
FieldName,
'totalCount'
>
totalCount?: never
}) &
(
| {
nodes: ConnectionFieldResolver<TypeName, FieldName, 'nodes'>
nodesBatch?: never
}
| {
nodes?: never
nodesBatch: BatchConnectionFieldResolver<TypeName, FieldName, 'nodes'>
})
export const connectionType = dynamicOutputMethod({
name: 'connectionField',
typeDefinition: `<FieldName extends string>(
fieldName: FieldName,
config: ConnectionFieldOpts<TypeName, FieldName>
): void;`,
factory({ typeDef: t, args, typeName }) {
const [fieldName, config] = args as [
string,
ConnectionFieldOpts<string, string>
]
const type =
typeof config.type === 'string' ? config.type : (config.type.name as any)
const filters = config.filters || {}
if (config.list) {
throw new Error(
`Connection field ${fieldName}.${type} cannot be used as a list.`
)
}
if (!basicConnectionMap.has(type)) {
basicConnectionMap.set(
type,
objectType({
name: `${type}Connection`,
definition(c) {
c.int('totalCount')
c.list.field('nodes', { type })
},
})
)
}
const FieldName = _.upperFirst(fieldName)
t.field(fieldName, {
type: basicConnectionMap.get(type)!,
args: {
input: paginatedInputType({
name: `${typeName}${FieldName}ConnectionInput`,
description: `Input for the ${typeName}.${fieldName} field`,
definition(t) {
_.each(filters, (val, key) => {
t.field(key, val)
})
},
}),
},
nullable: config.nullable,
description: config.description,
resolve(root, args, ctx) {
const nodesResolver: GraphQLFieldResolver<any, any> = config.nodes
? (...fArgs) => config.nodes(root, args.input || {}, ctx, fArgs[3])
: createBatchResolver((batched, _args, ctx, info) => {
return config.nodesBatch(batched, args.input, ctx, info)
})
const totalCountResolver: GraphQLFieldResolver<
any,
any
> = config.totalCount
? (...fArgs) =>
config.totalCount(root, args.input || {}, ctx, fArgs[3])
: createBatchResolver((batched, _args, ctx, info) => {
return config.totalCountBatch(batched, args.input, ctx, info)
})
return {
nodes: nodesResolver,
totalCount: totalCountResolver,
}
},
})
},
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment