diff --git a/src/builder.ts b/src/builder.ts index b23c3302..c63704aa 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -159,6 +159,7 @@ import { NexusDirectiveDef, } from './definitions/directive' import { rebuildNamedType, RebuildConfig } from './rebuildType' +import type { Filters } from './printSchemaWithDirectives' type NexusShapedOutput = { name: string @@ -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 /** diff --git a/src/makeSchema.ts b/src/makeSchema.ts index 6781b1cd..5c2c8310 100644 --- a/src/makeSchema.ts +++ b/src/makeSchema.ts @@ -162,6 +162,7 @@ function extractGraphQLSchemaOptions( sourceTypes, prettierConfig, plugins, + filters, customPrintSchemaFn, features, contextType, diff --git a/src/printSchemaWithDirectives.ts b/src/printSchemaWithDirectives.ts index 9a13bf59..5d43cf27 100644 --- a/src/printSchemaWithDirectives.ts +++ b/src/printSchemaWithDirectives.ts @@ -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) diff --git a/src/typegenMetadata.ts b/src/typegenMetadata.ts index 336ab046..ffad39d1 100644 --- a/src/typegenMetadata.ts +++ b/src/typegenMetadata.ts @@ -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') } diff --git a/tests/__snapshots__/makeSchema.spec.ts.snap b/tests/__snapshots__/makeSchema.spec.ts.snap index 98330682..596869b4 100644 --- a/tests/__snapshots__/makeSchema.spec.ts.snap +++ b/tests/__snapshots__/makeSchema.spec.ts.snap @@ -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 diff --git a/tests/makeSchema.spec.ts b/tests/makeSchema.spec.ts index 96425a70..de22a1c9 100644 --- a/tests/makeSchema.spec.ts +++ b/tests/makeSchema.spec.ts @@ -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 @@ -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() + }) + }) })