Skip to content

Commit

Permalink
feat: add onObjectDefinition / onInputObjectDefinition (graphql-nexus…
Browse files Browse the repository at this point in the history
  • Loading branch information
tgriesser authored Oct 7, 2020
1 parent 0dd8ea3 commit b4e0deb
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 17 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
run: yarn -s test:ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '10.x'
with:
directory: ./coverage

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/trunk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- name: Test
run: yarn -s test:ci
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '10.x'
uses: codecov/codecov-action@v1
with:
directory: ./coverage
Expand Down
7 changes: 6 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
codecov:
require_ci_to_pass: no
notify:
wait_for_ci: no

comment:
layout: "diff"
layout: 'diff'

coverage:
status:
Expand Down
43 changes: 43 additions & 0 deletions docs/content/015-api/110-plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,49 @@ plugin({
})
```

### onObjectDefinition(t, objectConfig)

The "onObjectDefinition" hook is called when an `objectType` is created, and is provided `t`, the object
passed into the `definition` block, as well as the `config` of the object.

```ts
export const NodePlugin = plugin({
name: 'NodePlugin',
description: 'Allows us to designate the field used to determine the "node" interface',
objectTypeDefTypes: `node?: string | core.FieldResolver<TypeName, any>`,
onObjectDefinition(t, { node }) {
if (node) {
let resolveFn
if (typeof node === 'string') {
const fieldResolve: FieldResolver<any, any> = (root, args, ctx, info) => {
return `${info.parentType.name}:${root[node]}`
}
resolveFn = fieldResolve
} else {
resolveFn = node
}
t.implements('Node')
t.id('id', {
nullable: false,
resolve: resolveFn,
})
}
},
})
```

Usage:

```ts
const User = objectType({
name: 'User',
node: 'id', // adds `id` field
definition(t) {
t.string('name')
},
})
```

### onCreateFieldResolver(config)

Every ObjectType, whether they are defined via Nexus' `objectType` api, or elsewhere is given a resolver.
Expand Down
49 changes: 48 additions & 1 deletion examples/kitchen-sink/src/example-plugins.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { plugin } from '@nexus/schema'
import { plugin, interfaceType, FieldResolver } from '@nexus/schema'

export const logMutationTimePlugin = plugin({
name: 'LogMutationTime',
Expand All @@ -15,3 +15,50 @@ export const logMutationTimePlugin = plugin({
}
},
})

export const NodePlugin = plugin({
name: 'NodePlugin',
description: 'Allows us to designate the field used to ',
objectTypeDefTypes: `node?: string | core.FieldResolver<TypeName, any>`,
onObjectDefinition(t, { node }) {
if (node) {
let resolveFn
if (typeof node === 'string') {
const fieldResolve: FieldResolver<any, any> = (root, args, ctx, info) => {
return `${info.parentType.name}:${root[node]}`
}
resolveFn = fieldResolve
} else {
resolveFn = node
}
t.implements('Node')
t.id('id', {
nullable: false,
resolve: resolveFn,
})
}
},
onMissingType(t, builder) {
if (t === 'Node') {
return interfaceType({
name: 'Node',
description:
'A "Node" is a field with a required ID field (id), per the https://relay.dev/docs/en/graphql-server-specification',
definition(t) {
t.id('id', {
nullable: false,
resolve: () => {
throw new Error('Abstract')
},
})
t.resolveType((t) => {
if (t.__typename) {
return t.__typename
}
throw new Error('__typename missing for resolving Node')
})
},
})
}
},
})
3 changes: 2 additions & 1 deletion examples/kitchen-sink/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ApolloServer } from 'apollo-server'
import { separateOperations } from 'graphql'
import { fieldExtensionsEstimator, getComplexity, simpleEstimator } from 'graphql-query-complexity'
import path from 'path'
import { logMutationTimePlugin } from './example-plugins'
import { logMutationTimePlugin, NodePlugin } from './example-plugins'
import * as types from './kitchen-sink-definitions'

const DEBUGGING_CURSOR = false
Expand All @@ -24,6 +24,7 @@ const schema = makeSchema({
typegen: path.join(__dirname, './kitchen-sink.gen.ts'),
},
plugins: [
NodePlugin,
connectionPlugin({
encodeCursor: fn,
decodeCursor: fn,
Expand Down
13 changes: 1 addition & 12 deletions examples/kitchen-sink/src/kitchen-sink-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,6 @@ export const testArgs2 = {
bar: idArg(),
}

export const Node = interfaceType({
name: 'Node',
definition(t) {
t.id('id', {
nullable: false,
resolve: () => {
throw new Error('Abstract')
},
})
},
})

export const Mutation = mutationType({
definition(t) {
t.boolean('ok', () => true)
Expand Down Expand Up @@ -118,6 +106,7 @@ export const TestUnion = unionType({

export const TestObj = objectType({
name: 'TestObj',
node: (obj) => `TestObj:${obj.item}`,
definition(t) {
t.implements('Bar', Baz)
t.string('item')
Expand Down
15 changes: 15 additions & 0 deletions examples/ts-ast-reader/ts-ast-reader-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ enum NodeFlags {
Namespace
NestedNamespace
None
OptionalChain
PermanentlySetIncrementalFlags
PossiblyContainsDynamicImport
PossiblyContainsImportMeta
Expand All @@ -621,6 +622,7 @@ enum NodeFlags {
Synthesized
ThisNodeHasError
ThisNodeOrAnySubNodesHasError
TypeCached
TypeExcludesFlags
UNKNOWN
YieldContext
Expand Down Expand Up @@ -842,6 +844,7 @@ enum SyntaxKind {
ArrowFunction
AsExpression
AsKeyword
AssertsKeyword
AsteriskAsteriskEqualsToken
AsteriskAsteriskToken
AsteriskEqualsToken
Expand Down Expand Up @@ -942,6 +945,7 @@ enum SyntaxKind {
FirstNode
FirstPunctuation
FirstReservedWord
FirstStatement
FirstTemplateToken
FirstToken
FirstTriviaToken
Expand Down Expand Up @@ -994,12 +998,17 @@ enum SyntaxKind {
JSDocComment
JSDocEnumTag
JSDocFunctionType
JSDocImplementsTag
JSDocNamepathType
JSDocNonNullableType
JSDocNullableType
JSDocOptionalType
JSDocParameterTag
JSDocPrivateTag
JSDocPropertyTag
JSDocProtectedTag
JSDocPublicTag
JSDocReadonlyTag
JSDocReturnTag
JSDocSignature
JSDocTag
Expand Down Expand Up @@ -1037,6 +1046,7 @@ enum SyntaxKind {
LastLiteralToken
LastPunctuation
LastReservedWord
LastStatement
LastTemplateToken
LastToken
LastTriviaToken
Expand All @@ -1063,6 +1073,7 @@ enum SyntaxKind {
MultiLineCommentTrivia
NamedExports
NamedImports
NamespaceExport
NamespaceExportDeclaration
NamespaceImport
NamespaceKeyword
Expand Down Expand Up @@ -1097,6 +1108,7 @@ enum SyntaxKind {
PlusToken
PostfixUnaryExpression
PrefixUnaryExpression
PrivateIdentifier
PrivateKeyword
PropertyAccessExpression
PropertyAssignment
Expand All @@ -1105,6 +1117,8 @@ enum SyntaxKind {
ProtectedKeyword
PublicKeyword
QualifiedName
QuestionDotToken
QuestionQuestionToken
QuestionToken
ReadonlyKeyword
RegularExpressionLiteral
Expand Down Expand Up @@ -1133,6 +1147,7 @@ enum SyntaxKind {
SymbolKeyword
SyntaxList
SyntheticExpression
SyntheticReferenceExpression
TaggedTemplateExpression
TemplateExpression
TemplateHead
Expand Down
22 changes: 22 additions & 0 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,16 @@ export class SchemaBuilder {
*/
protected onAfterBuildFns: Exclude<PluginConfig['onAfterBuild'], undefined>[] = []

/**
* Executed after the object is defined, allowing us to add additional fields to the object
*/
protected onObjectDefinitionFns: Exclude<PluginConfig['onObjectDefinition'], undefined>[] = []

/**
* Executed after the object is defined, allowing us to add additional fields to the object
*/
protected onInputObjectDefinitionFns: Exclude<PluginConfig['onInputObjectDefinition'], undefined>[] = []

/**
* The `schemaExtension` is created just after the types are walked,
* but before the fields are materialized.
Expand Down Expand Up @@ -673,6 +683,12 @@ export class SchemaBuilder {
if (pluginConfig.onAfterBuild) {
this.onAfterBuildFns.push(pluginConfig.onAfterBuild)
}
if (pluginConfig.onObjectDefinition) {
this.onObjectDefinitionFns.push(pluginConfig.onObjectDefinition)
}
if (pluginConfig.onInputObjectDefinition) {
this.onInputObjectDefinitionFns.push(pluginConfig.onInputObjectDefinition)
}
})
}

Expand Down Expand Up @@ -761,6 +777,9 @@ export class SchemaBuilder {
warn: consoleWarn,
})
config.definition(definitionBlock)
this.onInputObjectDefinitionFns.forEach((fn) => {
fn(definitionBlock, config)
})
const extensions = this.inputTypeExtendMap[config.name]
if (extensions) {
extensions.forEach((extension) => {
Expand Down Expand Up @@ -790,6 +809,9 @@ export class SchemaBuilder {
warn: consoleWarn,
})
config.definition(definitionBlock)
this.onObjectDefinitionFns.forEach((fn) => {
fn(definitionBlock, config)
})
const extensions = this.typeExtendMap[config.name]
if (extensions) {
extensions.forEach((extension) => {
Expand Down
18 changes: 18 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
} from './definitions/_types'
import { NexusSchemaExtension } from './extensions'
import { isPromiseLike, PrintedGenTyping, PrintedGenTypingImport, venn } from './utils'
import { NexusObjectTypeConfig, ObjectDefinitionBlock } from './definitions/objectType'
import { InputDefinitionBlock } from './definitions/definitionBlocks'
import { NexusInputObjectTypeConfig } from './definitions/inputObjectType'

export { PluginBuilderLens }

Expand Down Expand Up @@ -89,6 +92,19 @@ export interface PluginConfig {
* After the schema is built, provided the Schema to do any final config validation.
*/
onAfterBuild?: (schema: GraphQLSchema) => void
/**
* Called immediately after the object is defined, allows for using metadata
* to define the shape of the object.
*/
onObjectDefinition?: (block: ObjectDefinitionBlock<any>, objectConfig: NexusObjectTypeConfig<any>) => void
/**
* Called immediately after the input object is defined, allows for using metadata
* to define the shape of the input object
*/
onInputObjectDefinition?: (
block: InputDefinitionBlock<any>,
objectConfig: NexusInputObjectTypeConfig<any>
) => void
/**
* If a type is not defined in the schema, our plugins can register an `onMissingType` handler,
* which will intercept the missing type name and give us an opportunity to respond with a valid
Expand Down Expand Up @@ -206,6 +222,8 @@ function validatePluginConfig(pluginConfig: PluginConfig): void {
'onBeforeBuild',
'onMissingType',
'onAfterBuild',
'onObjectDefinition',
'onInputObjectDefinition',
]
const validOptionalProps = ['description', 'fieldDefTypes', 'objectTypeDefTypes', ...optionalPropFns]

Expand Down
Loading

0 comments on commit b4e0deb

Please sign in to comment.