improve: remove dynamic output builtins (#590)

closes #581 

BREAKING CHANGE:

`ext` is no longer exported. The `relayConnectionField` and `collectionField` dynamic output methods have been removed. In their place try the `connection` plugin.
This commit is contained in:
Jason Kuhrt 2020-10-30 10:19:15 -04:00 committed by GitHub
parent cc12ec16fb
commit 9f01342248
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 29 additions and 339 deletions

View File

@ -13,7 +13,7 @@
"trailingComma": "all"
},
"dependencies": {
"@nexus/schema": "0.17.0-next.2",
"@nexus/schema": "0.17.0",
"graphql": "^15.3.0"
},
"devDependencies": {

View File

@ -2,10 +2,10 @@
# yarn lockfile v1
"@nexus/schema@0.17.0-next.2":
version "0.17.0-next.2"
resolved "https://registry.yarnpkg.com/@nexus/schema/-/schema-0.17.0-next.2.tgz#b85fcb1cd35d4fd65618404f406d0e1e309ffda7"
integrity sha512-EMUYhEvo6DkbZSBC/ErlSN59KWDVrZbuBbRY+QbRn2EsOcOhA3Y6g+/SDYEfJwqJzKjiC9AjqOmOqzmBXBNJQA==
"@nexus/schema@0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@nexus/schema/-/schema-0.17.0.tgz#5b6ec9630a9cd17f64a7ffc999378fa9835df3a9"
integrity sha512-X/DdvXGDNw2VMi1F2un7JRY17yFbuxXHM+U4ViejAf9jCkhlasdljK3TLTqA5wSFfriFIx0eWtYHkUeOvIjLkg==
dependencies:
iterall "^1.2.2"
tslib "^1.9.3"

View File

@ -1,60 +0,0 @@
import { GraphQLFieldResolver } from 'graphql'
import { intArg } from '../definitions/args'
import { NexusObjectTypeDef, objectType } from '../definitions/objectType'
import { dynamicOutputMethod } from '../dynamicMethod'
const basicCollectionMap = new Map<string, NexusObjectTypeDef<string>>()
export const CollectionFieldMethod = dynamicOutputMethod({
name: 'collectionField',
typeDefinition: `<FieldName extends string>(fieldName: FieldName, opts: {
type: NexusGenObjectNames | NexusGenInterfaceNames | core.NexusObjectTypeDef<any> | core.NexusInterfaceTypeDef<any>,
nodes: core.SubFieldResolver<TypeName, FieldName, "nodes">,
totalCount: core.SubFieldResolver<TypeName, FieldName, "totalCount">,
args?: core.ArgsRecord,
nullable?: boolean,
description?: string
}): void;`,
factory({ typeDef: t, args: [fieldName, config] }) {
/* istanbul ignore next */
if (!config.type) {
throw new Error(`Missing required property "type" from collectionField ${fieldName}`)
}
const typeName = typeof config.type === 'string' ? config.type : config.type.name
/* istanbul ignore next */
if (config.list) {
throw new Error(`Collection field ${fieldName}.${typeName} cannot be used as a list.`)
}
if (!basicCollectionMap.has(typeName)) {
basicCollectionMap.set(
typeName,
objectType({
name: `${typeName}Collection`,
definition(c) {
c.int('totalCount')
c.list.field('nodes', { type: config.type })
},
})
)
}
t.field(fieldName, {
type: basicCollectionMap.get(typeName)!,
args: config.args || {
page: intArg(),
perPage: intArg(),
},
nullable: config.nullable,
description: config.description,
resolve(root, args, ctx, info) {
const nodesResolver: GraphQLFieldResolver<any, any> = (...fArgs) =>
config.nodes(root, args, ctx, fArgs[3])
const totalCountResolver: GraphQLFieldResolver<any, any> = (...fArgs) =>
config.totalCount(root, args, ctx, fArgs[3])
return {
nodes: nodesResolver,
totalCount: totalCountResolver,
}
},
})
},
})

View File

@ -1,2 +0,0 @@
export * from './collection'
export * from './relayConnection'

View File

@ -1,83 +0,0 @@
import { GraphQLFieldResolver } from 'graphql'
import { intArg, stringArg } from '../definitions/args'
import { NexusObjectTypeDef, objectType } from '../definitions/objectType'
import { dynamicOutputMethod } from '../dynamicMethod'
const relayConnectionMap = new Map<string, NexusObjectTypeDef<string>>()
let pageInfo: NexusObjectTypeDef<string>
export const RelayConnectionFieldMethod = dynamicOutputMethod({
name: 'relayConnectionField',
typeDefinition: `<FieldName extends string>(fieldName: FieldName, opts: {
type: NexusGenObjectNames | NexusGenInterfaceNames | core.NexusObjectTypeDef<any> | core.NexusInterfaceTypeDef<any>,
edges: core.SubFieldResolver<TypeName, FieldName, "edges">,
pageInfo: core.SubFieldResolver<TypeName, FieldName, "pageInfo">,
args?: Record<string, core.NexusArgDef<any>>,
nullable?: boolean,
description?: string
}): void
`,
factory({ typeDef: t, args: [fieldName, config] }) {
/* istanbul ignore next */
if (!config.type) {
throw new Error(`Missing required property "type" from relayConnection field ${fieldName}`)
}
const typeName = typeof config.type === 'string' ? config.type : config.type.name
pageInfo =
pageInfo ||
objectType({
name: `ConnectionPageInfo`,
definition(p) {
p.boolean('hasNextPage')
p.boolean('hasPreviousPage')
},
})
/* istanbul ignore next */
if (config.list) {
throw new Error(`Collection field ${fieldName}.${typeName} cannot be used as a list.`)
}
if (!relayConnectionMap.has(typeName)) {
relayConnectionMap.set(
typeName,
objectType({
name: `${typeName}RelayConnection`,
definition(c) {
c.list.field('edges', {
type: objectType({
name: `${typeName}Edge`,
definition(e) {
e.id('cursor')
e.field('node', { type: config.type })
},
}),
})
c.field('pageInfo', { type: pageInfo })
},
})
)
}
t.field(fieldName, {
type: relayConnectionMap.get(typeName)!,
args: {
first: intArg(),
after: stringArg(),
last: intArg(),
before: stringArg(),
...config.args,
},
nullable: config.nullable,
description: config.description,
resolve(root, args, ctx, info) {
const edgeResolver: GraphQLFieldResolver<any, any> = (...fArgs) =>
config.edges(root, args, ctx, fArgs[3])
const pageInfoResolver: GraphQLFieldResolver<any, any> = (...fArgs) =>
config.pageInfo(root, args, ctx, fArgs[3])
return {
edges: edgeResolver,
pageInfo: pageInfoResolver,
}
},
})
},
})

View File

@ -1,3 +1,6 @@
import * as blocks from './blocks'
import * as core from './core'
// All of the Public API definitions
export { makeSchema } from './builder'
export { arg, booleanArg, floatArg, idArg, intArg, stringArg } from './definitions/args'
@ -21,7 +24,4 @@ export * from './plugins'
export { convertSDL } from './sdlConverter'
export { AllInputTypes, AllOutputTypes, FieldResolver, FieldType } from './typegenTypeHelpers'
export { groupTypes } from './utils'
export { core, blocks, ext }
import * as blocks from './blocks'
import * as core from './core'
import * as ext from './dynamicMethods'
export { blocks, core }

View File

@ -1,48 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`dynamicOutputMethod CollectionFieldMethod example 1`] = `
Object {
"data": Object {
"cats": Object {
"nodes": Array [
Object {
"id": "Cat:1",
"name": "Felix",
},
Object {
"id": "Cat:2",
"name": "Booker",
},
],
"totalCount": 2,
},
},
}
`;
exports[`dynamicOutputMethod RelayConnectionFieldMethod example 1`] = `
Object {
"data": Object {
"cats": Object {
"edges": Array [
Object {
"node": Object {
"id": "Cat:1",
"name": "Felix",
},
},
Object {
"node": Object {
"id": "Cat:2",
"name": "Booker",
},
},
],
"pageInfo": Object {
"hasNextPage": false,
"hasPreviousPage": false,
},
},
},
}
`;

View File

@ -1,161 +1,44 @@
import { graphql } from 'graphql'
import { GraphQLDateTime } from 'graphql-scalars'
import path from 'path'
import {
decorateType,
dynamicInputMethod,
ext,
dynamicOutputMethod,
inputObjectType,
makeSchema,
objectType,
queryType,
} from '../src'
import { dynamicOutputProperty } from '../src/dynamicProperty'
import { CatListFixture } from './_fixtures'
beforeEach(() => {
jest.clearAllMocks()
})
describe('dynamicOutputMethod', () => {
const Cat = objectType({
name: 'Cat',
definition(t) {
t.id('id')
t.string('name')
},
})
test('RelayConnectionFieldMethod example', async () => {
const Query = queryType({
definition(t) {
// @ts-ignore
t.relayConnectionField('cats', {
type: Cat,
pageInfo: () => ({
hasNextPage: false,
hasPreviousPage: false,
}),
edges: () => CatListFixture.map((c) => ({ cursor: `Cursor: ${c.id}`, node: c })),
})
},
})
const schema = makeSchema({
types: [Query, ext.RelayConnectionFieldMethod],
it('should provide a method on the output type builder', async () => {
makeSchema({
types: [
dynamicOutputMethod({
name: 'foo',
typeDefinition: 'String',
factory({ typeDef }) {
typeDef.field('viaFoo', { type: 'Int' })
},
}),
objectType({
name: 'Bar',
definition(t) {
//@ts-expect-error
t.foo()
},
}),
],
outputs: {
typegen: path.join(__dirname, 'test-output.ts'),
schema: path.join(__dirname, 'schema.graphql'),
},
shouldGenerateArtifacts: false,
})
expect(
await graphql(
schema,
`
{
cats {
edges {
node {
id
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}
`
)
).toMatchSnapshot()
})
test('CollectionFieldMethod example', async () => {
const dynamicOutputMethod = queryType({
definition(t) {
// @ts-ignore
t.collectionField('cats', {
type: Cat,
totalCount: () => CatListFixture.length,
nodes: () => CatListFixture,
})
},
})
const schema = makeSchema({
types: [dynamicOutputMethod, ext.CollectionFieldMethod],
outputs: {
typegen: path.join(__dirname, 'test-output'),
schema: path.join(__dirname, 'schema.graphql'),
},
shouldGenerateArtifacts: false,
})
expect(
await graphql(
schema,
`
{
cats {
totalCount
nodes {
id
name
}
}
}
`
)
).toMatchSnapshot()
})
test('CollectionFieldMethod example with string type ref', () => {
makeSchema({
types: [
queryType({
definition(t) {
// @ts-ignore
t.collectionField('cats', {
type: 'Cat',
totalCount: () => CatListFixture.length,
nodes: () => CatListFixture,
})
},
}),
ext.CollectionFieldMethod,
],
outputs: false,
})
})
test('RelayConnectionFieldMethod example with string type ref', async () => {
makeSchema({
types: [
queryType({
definition(t) {
// @ts-ignore
t.relayConnectionField('cats', {
type: 'Cat',
nodes(root: any, args: any, ctx: any, info: any) {
return CatListFixture
},
pageInfo: () => ({
hasNextPage: false,
hasPreviousPage: false,
}),
edges: () =>
CatListFixture.map((c) => ({
cursor: `Cursor: ${c.id}`,
node: c,
})),
})
},
}),
ext.RelayConnectionFieldMethod,
],
outputs: false,
})
})
})
@ -170,7 +53,7 @@ describe('dynamicInputMethod', () => {
name: 'SomeInput',
definition(t) {
t.id('id')
// @ts-ignore
//@ts-expect-error
t.timestamps()
},
}),