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

[Feature] Add Support for Directive filters and Type filters to cover AWS AppSync use-cases #1143

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ import {
NexusDirectiveDef,
} from './definitions/directive'
import { rebuildNamedType, RebuildConfig } from './rebuildType'
import type { Filters } from './printSchemaWithDirectives'

type NexusShapedOutput = {
name: string
Expand Down Expand Up @@ -324,6 +325,11 @@ export interface BuilderConfigInput {
plugins?: NexusPlugin[]
/** Provide if you wish to customize the behavior of the schema printing. Otherwise, uses `printSchema` from graphql-js */
customPrintSchemaFn?: typeof printSchema
/** Add filters to skip defining directives / types for the usecase when they are already available within the environment for e.g
* with AWS AppSync, we have scaler type `AWS Timestamp` already defined, similary directives like `aws_lambda` are already
* available etc.
*/
filters?: Filters
/** Customize and toggle on or off various features of Nexus. */
features?: NexusFeaturesInput
/**
Expand Down
1 change: 1 addition & 0 deletions src/makeSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ function extractGraphQLSchemaOptions(
sourceTypes,
prettierConfig,
plugins,
filters,
customPrintSchemaFn,
features,
contextType,
Expand Down
22 changes: 16 additions & 6 deletions src/printSchemaWithDirectives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,28 @@ import {

import { astFromValue } from 'graphql'

export function printSchemaWithDirectives(schema: GraphQLSchema): string {
return printFilteredSchemaWithDirectives(schema, (n) => !isSpecifiedDirective(n), isDefinedType)
type DirectiveFilter = (directive: GraphQLDirective) => boolean
type TypeFilter = (type: GraphQLNamedType) => boolean

export type Filters = {
isEnvironmentDefinedDirective?: DirectiveFilter
isEnvironmentDefinedType?: TypeFilter
}

function isDefinedType(type: GraphQLNamedType): boolean {
return !isSpecifiedScalarType(type) && !isIntrospectionType(type)
export function printSchemaWithDirectives(schema: GraphQLSchema, filters?: Filters): string {
const directiveFilter: DirectiveFilter = (directive) =>
!isSpecifiedDirective(directive) && (!filters?.isEnvironmentDefinedDirective?.(directive) ?? true)
const typeFilter: TypeFilter = (type) =>
!isSpecifiedScalarType(type) &&
!isIntrospectionType(type) &&
(!filters?.isEnvironmentDefinedType?.(type) ?? true)
return printFilteredSchemaWithDirectives(schema, directiveFilter, typeFilter)
}

function printFilteredSchemaWithDirectives(
schema: GraphQLSchema,
directiveFilter: (type: GraphQLDirective) => boolean,
typeFilter: (type: GraphQLNamedType) => boolean
directiveFilter: DirectiveFilter,
typeFilter: TypeFilter
): string {
const directives = schema.getDirectives().filter(directiveFilter)
const types = Object.values(schema.getTypeMap()).filter(typeFilter)
Expand Down
2 changes: 1 addition & 1 deletion src/typegenMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class TypegenMetadata {
generateSchemaFile(schema: GraphQLSchema): string {
let printedSchema = this.config.customPrintSchemaFn
? this.config.customPrintSchemaFn(schema)
: printSchemaWithDirectives(schema)
: printSchemaWithDirectives(schema, this.config.filters)
return [SDL_HEADER, printedSchema].join('\n\n')
}

Expand Down
33 changes: 33 additions & 0 deletions tests/__snapshots__/makeSchema.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,39 @@ type Query {
}"
`;

exports[`makeSchema filters can specify custom filters for directives 1`] = `
"### This file was generated by Nexus Schema
### Do not make changes to this file directly


directive @some_other_directive on OBJECT

type Query {
ok: Boolean!
}

type Random @aws_lambda @some_other_directive {
randomId: String!
}"
`;

exports[`makeSchema filters can specify custom filters for types 1`] = `
"### This file was generated by Nexus Schema
### Do not make changes to this file directly


type Query {
ok: Boolean!
}

type Random {
expiry: AWSTimestamp
expiry2: some_other_scaler
}

scalar some_other_scaler"
`;

exports[`makeSchema shouldExitAfterGenerateArtifacts accepts a customPrintSchemaFn 1`] = `
"### This file was generated by Nexus Schema
### Do not make changes to this file directly
Expand Down
78 changes: 77 additions & 1 deletion tests/makeSchema.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { DirectiveLocation, GraphQLDirective, GraphQLInt, GraphQLSchema, printSchema } from 'graphql'
import os from 'os'
import path from 'path'
import { objectType } from '../src'
import { directive, objectType, scalarType } from '../src'
import { generateSchema, makeSchema } from '../src/makeSchema'
import { queryField } from '../src/definitions/queryField'
import { readFile } from 'fs'
import { promisify } from 'util'

export type Test = {
id: string
Expand Down Expand Up @@ -184,4 +186,78 @@ describe('makeSchema', () => {
expect(printSchema(schema).trim()).toMatchSnapshot()
})
})

describe('filters', () => {
it('can specify custom filters for directives', async () => {
const pathToSchema = path.join(os.tmpdir(), '/schema1.graphql')

const awsLambdaDirective = directive({
name: 'aws_lambda',
locations: ['OBJECT'],
})

const someOtherDirective = directive({
name: 'some_other_directive',
locations: ['OBJECT'],
})

const random = objectType({
name: 'Random',
directives: [awsLambdaDirective, someOtherDirective],
definition(t) {
t.nonNull.string('randomId')
},
})

await makeSchema({
types: [random],
filters: {
isEnvironmentDefinedDirective: (directive) => {
return directive.name === 'aws_lambda'
},
},
outputs: {
schema: pathToSchema,
},
})

const graphql = await promisify(readFile)(pathToSchema, 'utf-8')
expect(graphql.trim()).toMatchSnapshot()
})

it('can specify custom filters for types', async () => {
const pathToSchema = path.join(os.tmpdir(), '/schema2.graphql')

const awsTimestampScaler = scalarType({
name: 'AWSTimestamp',
})

const someOtherScaler = scalarType({
name: 'some_other_scaler',
})

const random = objectType({
name: 'Random',
definition(t) {
t.field('expiry', { type: awsTimestampScaler })
t.field('expiry2', { type: someOtherScaler })
},
})

await makeSchema({
types: [random],
filters: {
isEnvironmentDefinedType: (type) => {
return type.name === 'AWSTimestamp'
},
},
outputs: {
schema: pathToSchema,
},
})

const graphql = await promisify(readFile)(pathToSchema, 'utf-8')
expect(graphql.trim()).toMatchSnapshot()
})
})
})