From 2606f32674f75513726207ceb198c906c276e90c Mon Sep 17 00:00:00 2001 From: Andrey Lunyov Date: Mon, 30 Sep 2019 06:11:31 -0700 Subject: [PATCH] Refactor all graphql-js usages to use Relay Schema Interface Summary: This diff is pretty straightforward. The first major change is in `GraphQLIR.js` where all GraphQLTypes replaced with the opaque TypeID from the Schema module. This change enforces, triggers a waterfall of updates (due to Flow errors) in Relay Transforms, Code Generators and RelayParsers (and other related helpers/modules). Next change is in the `GraphQLCompilerContext` which no longer have references to the `server` and `client` schemas. Instead, it receives an instance of Schema, and expose it via `getSchema()` method. To correctly handle this change, I have to update unit-tests in the compiler, so they are getting flow coverage. Reviewed By: josephsavona Differential Revision: D17433238 fbshipit-source-id: 582618875a6612f30deb51512c0421b9b373abfd --- .../relay-compiler/bin/RelayCompilerMain.js | 50 +-- .../relay-compiler/codegen/CodegenRunner.js | 18 +- .../codegen/NormalizationCodeGenerator.js | 142 +++++--- .../codegen/ReaderCodeGenerator.js | 141 +++++--- .../codegen/RelayCodeGenerator.js | 23 +- .../relay-compiler/codegen/RelayFileWriter.js | 41 +-- .../__tests__/compileRelayArtifacts-test.js | 11 +- .../codegen/compileRelayArtifacts.js | 9 +- .../codegen/writeRelayGeneratedFile.js | 3 + packages/relay-compiler/core/ASTConvert.js | 13 +- .../core/GraphQLCompilerContext.js | 17 +- packages/relay-compiler/core/GraphQLIR.js | 44 +-- .../relay-compiler/core/GraphQLIRPrinter.js | 219 +++++++------ .../relay-compiler/core/GraphQLIRVisitor.js | 4 +- .../relay-compiler/core/GraphQLSchemaUtils.js | 296 ----------------- .../relay-compiler/core/RelayCompilerScope.js | 14 +- packages/relay-compiler/core/RelayParser.js | 303 +++++++++--------- .../core/RelayRecordIDFieldDefinition.js | 28 -- .../relay-compiler/core/RelayValidator.js | 9 +- .../__tests__/GraphQLCompilerContext-test.js | 16 +- .../__tests__/GraphQLIRTransformer-test.js | 5 +- .../core/__tests__/GraphQLIRValidator-test.js | 5 +- .../core/__tests__/GraphQLIRVisitor-test.js | 13 +- .../core/__tests__/RelayCompilerScope-test.js | 51 +-- .../core/__tests__/RelayParser-test.js | 28 +- .../core/__tests__/RelayPrinter-test.js | 8 +- .../core/__tests__/RelayValidator-test.js | 7 +- .../__snapshots__/RelayParser-test.js.snap | 14 +- .../__tests__/filterContextForNode-test.js | 9 +- .../core/filterContextForNode.js | 7 +- .../relay-compiler/core/getFieldDefinition.js | 104 +++--- .../core/getIdentifierForArgumentValue.js | 2 +- .../core/getIdentifierForSelection.js | 9 +- .../core/getLiteralArgumentValues.js | 2 +- .../core/inferRootArgumentDefinitions.js | 43 ++- .../relay-compiler/core/isEquivalentType.js | 53 --- .../connection/RelayConnectionTransform.js | 161 +++++----- .../RelayConnectionTransform-test.js | 13 +- packages/relay-compiler/index.js | 11 +- .../language/RelayLanguagePluginInterface.js | 12 +- .../language/javascript/RelayFlowGenerator.js | 156 +++++---- .../javascript/RelayFlowTypeTransformers.js | 149 +++++---- .../__tests__/RelayFlowGenerator-test.js | 36 ++- .../transforms/ClientExtensionsTransform.js | 88 ++--- .../transforms/ConnectionFieldTransform.js | 47 ++- .../transforms/FilterDirectivesTransform.js | 4 +- .../transforms/FlattenTransform.js | 83 +++-- .../transforms/InlineFragmentsTransform.js | 2 +- .../RelayApplyFragmentArgumentTransform.js | 2 + .../transforms/RelayDeferStreamTransform.js | 17 +- .../transforms/RelayFieldHandleTransform.js | 25 +- .../RelayGenerateIDFieldTransform.js | 62 ++-- .../RelayGenerateTypeNameTransform.js | 14 +- .../transforms/RelayMaskTransform.js | 5 +- .../transforms/RelayMatchTransform.js | 155 +++++---- .../RelayRefetchableFragmentTransform.js | 176 +++++----- .../RelaySplitModuleImportTransform.js | 9 +- .../transforms/RelayTestOperationTransform.js | 34 +- .../SkipClientExtensionsTransform.js | 4 +- .../transforms/SkipRedundantNodesTransform.js | 10 +- .../ValidateGlobalVariablesTransform.js | 7 +- .../ClientExtensionsTransform-test.js | 14 +- .../FilterDirectivesTransform-test.js | 10 +- .../__tests__/FlattenTransform-test.js | 11 +- .../InlineDataFragmentTransform-test.js | 13 +- .../InlineFragmentsTransform-test.js | 8 +- ...elayApplyFragmentArgumentTransform-test.js | 8 +- .../RelayDeferStreamTransform-test.js | 17 +- .../RelayFieldHandleTransform-test.js | 6 +- .../RelayGenerateIDFieldTransform-test.js | 8 +- .../RelayGenerateTypeNameTransform-test.js | 6 +- .../__tests__/RelayMaskTransform-test.js | 30 +- .../__tests__/RelayMatchTransform-test.js | 14 +- .../RelayRefetchableFragmentTransform-test.js | 13 +- .../RelayRelayDirectiveTransform-test.js | 6 +- .../RelaySkipHandleFieldTransform-test.js | 6 +- .../RelaySplitModuleImportTransform-test.js | 10 +- .../RelayTestOperationTransform-test.js | 6 +- .../SkipClientExtensionsTransform-test.js | 14 +- .../SkipRedundantNodesTransform-test.js | 14 +- .../SkipUnreachableNodeTransform-test.js | 8 +- .../SkipUnusedVariablesTransform-test.js | 6 +- .../util/joinArgumentDefinitions.js | 17 +- .../validateRelayRequiredArguments-test.js | 6 +- .../validateRelayServerOnlyDirectives-test.js | 4 +- .../validateRelayRequiredArguments.js | 48 +-- .../relay-test-utils-internal/TestCompiler.js | 10 +- .../parseGraphQLText.js | 9 +- 88 files changed, 1674 insertions(+), 1691 deletions(-) delete mode 100644 packages/relay-compiler/core/GraphQLSchemaUtils.js delete mode 100644 packages/relay-compiler/core/RelayRecordIDFieldDefinition.js delete mode 100644 packages/relay-compiler/core/isEquivalentType.js diff --git a/packages/relay-compiler/bin/RelayCompilerMain.js b/packages/relay-compiler/bin/RelayCompilerMain.js index 142a2f7968bf4..a66e4169d1b43 100644 --- a/packages/relay-compiler/bin/RelayCompilerMain.js +++ b/packages/relay-compiler/bin/RelayCompilerMain.js @@ -24,12 +24,7 @@ const fs = require('fs'); const invariant = require('invariant'); const path = require('path'); -const { - buildASTSchema, - buildClientSchema, - parse, - printSchema, -} = require('graphql'); +const {buildClientSchema, Source, printSchema} = require('graphql'); const { commonTransforms, @@ -42,7 +37,6 @@ const { import type {ScalarTypeMapping} from '../language/javascript/RelayFlowTypeTransformers'; import type {WriteFilesOptions} from '../codegen/CodegenRunner'; -import type {GraphQLSchema} from 'graphql'; import type { PluginInitializer, PluginInterface, @@ -94,7 +88,6 @@ function getFilepathsFromGlob( ): Array { const {extensions, include, exclude} = config; const patterns = include.map(inc => `${inc}/*.+(${extensions.join('|')})`); - const glob = require('fast-glob'); return glob.sync(patterns, { cwd: baseDir, @@ -143,7 +136,7 @@ function getLanguagePlugin( } else { languagePlugin = language; } - if (languagePlugin.default) { + if (languagePlugin.default != null) { // $FlowFixMe - Flow no longer considers statics of functions as any languagePlugin = languagePlugin.default; } @@ -188,12 +181,12 @@ function getPersistQueryFunction( } } -async function main(config: Config) { - if (config.verbose && config.quiet) { +async function main(defaultConfig: Config) { + if (defaultConfig.verbose && defaultConfig.quiet) { throw new Error("I can't be quiet and verbose at the same time"); } - config = getPathBasedConfig(config); + let config = getPathBasedConfig(defaultConfig); config = await getWatchConfig(config); // Use function from module.exports to be able to mock it for tests @@ -268,7 +261,7 @@ function getCodegenRunner(config: Config): CodegenRunner { verbose: config.verbose, quiet: config.quiet, }); - const schema = getSchema(config.schema); + const schema = getSchemaSource(config.schema); const languagePlugin = getLanguagePlugin(config.language); const persistQueryFunction = getPersistQueryFunction(config); const inputExtensions = config.extensions || languagePlugin.inputExtensions; @@ -283,7 +276,7 @@ function getCodegenRunner(config: Config): CodegenRunner { providedArtifactDirectory != null ? path.resolve(process.cwd(), providedArtifactDirectory) : null; - const generatedDirectoryName = artifactDirectory || '__generated__'; + const generatedDirectoryName = artifactDirectory ?? '__generated__'; const sourceSearchOptions = { extensions: inputExtensions, include: config.include, @@ -299,7 +292,7 @@ function getCodegenRunner(config: Config): CodegenRunner { baseDir: config.src, getFileFilter: sourceModuleParser.getFileFilter, getParser: sourceModuleParser.getParser, - getSchema: () => schema, + getSchemaSource: () => schema, watchmanExpression: config.watchman ? buildWatchExpression(sourceSearchOptions) : null, @@ -310,7 +303,7 @@ function getCodegenRunner(config: Config): CodegenRunner { graphql: { baseDir: config.src, getParser: DotGraphQLParser.getParser, - getSchema: () => schema, + getSchemaSource: () => schema, watchmanExpression: config.watchman ? buildWatchExpression(graphqlSearchOptions) : null, @@ -443,29 +436,18 @@ function getRelayFileWriter( }; } -function getSchema(schemaPath: string): GraphQLSchema { - try { - let source = fs.readFileSync(schemaPath, 'utf8'); - if (path.extname(schemaPath) === '.json') { - source = printSchema(buildClientSchema(JSON.parse(source).data)); - } - source = ` +function getSchemaSource(schemaPath: string): Source { + let source = fs.readFileSync(schemaPath, 'utf8'); + if (path.extname(schemaPath) === '.json') { + source = printSchema(buildClientSchema(JSON.parse(source).data)); + } + source = ` directive @include(if: Boolean) on FRAGMENT_SPREAD | FIELD | INLINE_FRAGMENT directive @skip(if: Boolean) on FRAGMENT_SPREAD | FIELD | INLINE_FRAGMENT ${source} `; - return buildASTSchema(parse(source), {assumeValid: true}); - } catch (error) { - throw new Error( - ` -Error loading schema. Expected the schema to be a .graphql or a .json -file, describing your GraphQL server's API. Error detail: - -${error.stack} - `.trim(), - ); - } + return new Source(source, schemaPath); } // Ensure that a watchman "root" file exists in the given directory diff --git a/packages/relay-compiler/codegen/CodegenRunner.js b/packages/relay-compiler/codegen/CodegenRunner.js index 7231afbe8b9ea..91e349bceb180 100644 --- a/packages/relay-compiler/codegen/CodegenRunner.js +++ b/packages/relay-compiler/codegen/CodegenRunner.js @@ -18,21 +18,23 @@ const Profiler = require('../core/GraphQLCompilerProfiler'); const invariant = require('invariant'); const path = require('path'); +const {create: createSchema} = require('../core/Schema'); // $FlowFixMe - importing immutable, which is untyped (and flow is sad about it) const {Map: ImmutableMap} = require('immutable'); import type ASTCache from '../core/ASTCache'; +import type {Schema} from '../core/Schema'; import type {GraphQLReporter} from '../reporters/GraphQLReporter'; import type {CompileResult, File} from './CodegenTypes'; import type {FileFilter, WatchmanExpression} from './CodegenWatcher'; import type {SourceControl} from './SourceControl'; -import type {DocumentNode, GraphQLSchema} from 'graphql'; +import type {DocumentNode, Source} from 'graphql'; export type ParserConfig = {| baseDir: string, getFileFilter?: (baseDir: string) => FileFilter, getParser: (baseDir: string) => ASTCache, - getSchema: () => GraphQLSchema, + getSchemaSource: () => Source, generatedDirectoriesWatchmanExpression?: ?WatchmanExpression, watchmanExpression?: ?WatchmanExpression, filepaths?: ?Array, @@ -58,7 +60,7 @@ type WriterConfigs = { export type WriteFilesOptions = {| onlyValidate: boolean, - schema: GraphQLSchema, + schema: Schema, documents: ImmutableMap, baseDocuments: ImmutableMap, sourceControl: ?SourceControl, @@ -273,7 +275,7 @@ class CodegenRunner { if (baseParsers) { baseParsers.forEach(baseParserName => { invariant( - this.parsers[baseParserName], + this.parsers[baseParserName] == null, 'Trying to access an uncompiled base parser config: %s', baseParserName, ); @@ -299,7 +301,11 @@ class CodegenRunner { // always create a new writer: we have to write everything anyways const documents = this.parsers[parser].documents(); const schema = Profiler.run('getSchema', () => - this.parserConfigs[parser].getSchema(), + createSchema( + this.parserConfigs[parser].getSchemaSource(), + baseDocuments.toArray(), + [], + ), ); const outputDirectories = await writeFiles({ @@ -384,7 +390,7 @@ class CodegenRunner { : anyFileFilter, async files => { invariant( - this.parsers[parserName], + this.parsers[parserName] == null, 'Trying to watch an uncompiled parser config: %s', parserName, ); diff --git a/packages/relay-compiler/codegen/NormalizationCodeGenerator.js b/packages/relay-compiler/codegen/NormalizationCodeGenerator.js index 23dd53f64ad0b..42e0a22f5ced2 100644 --- a/packages/relay-compiler/codegen/NormalizationCodeGenerator.js +++ b/packages/relay-compiler/codegen/NormalizationCodeGenerator.js @@ -10,13 +10,10 @@ 'use strict'; -const SchemaUtils = require('../core/GraphQLSchemaUtils'); - const { createCompilerError, createUserError, } = require('../core/RelayCompilerError'); -const {GraphQLList} = require('graphql'); const { ConnectionInterface, getStorageKey, @@ -31,7 +28,16 @@ import type { Root, Selection, SplitOperation, + LinkedField, + Defer, + Stream, + Condition, + Connection, + ConnectionField, + InlineFragment, + LocalArgumentDefinition, } from '../core/GraphQLIR'; +import type {Schema, TypeID} from '../core/Schema'; import type { NormalizationArgument, NormalizationDefer, @@ -47,7 +53,6 @@ import type { NormalizationSplitOperation, NormalizationStream, } from 'relay-runtime'; -const {getRawType, isAbstractType, getNullableType} = SchemaUtils; /** * @public @@ -55,16 +60,20 @@ const {getRawType, isAbstractType, getNullableType} = SchemaUtils; * Converts a GraphQLIR node into a plain JS object representation that can be * used at runtime. */ -declare function generate(node: Root): NormalizationOperation; -declare function generate(node: SplitOperation): NormalizationSplitOperation; +declare function generate(schema: Schema, node: Root): NormalizationOperation; +declare function generate( + schema: Schema, + node: SplitOperation, +): NormalizationSplitOperation; function generate( - node: $FlowFixMe, + schema: Schema, + node: Root | SplitOperation, ): NormalizationOperation | NormalizationSplitOperation { switch (node.kind) { case 'Root': - return generateRoot(node); + return generateRoot(schema, node); case 'SplitOperation': - return generateSplitOperation(node); + return generateSplitOperation(schema, node); default: throw createCompilerError( `NormalizationCodeGenerator: Unsupported AST kind '${node.kind}'.`, @@ -73,35 +82,44 @@ function generate( } } -function generateRoot(node: Root): NormalizationOperation { +function generateRoot(schema: Schema, node: Root): NormalizationOperation { return { kind: 'Operation', name: node.name, - argumentDefinitions: generateArgumentDefinitions(node.argumentDefinitions), - selections: generateSelections(node.selections), + argumentDefinitions: generateArgumentDefinitions( + schema, + node.argumentDefinitions, + ), + selections: generateSelections(schema, node.selections), }; } -function generateSplitOperation(node, key): NormalizationSplitOperation { +function generateSplitOperation( + schema: Schema, + node: SplitOperation, +): NormalizationSplitOperation { return { kind: 'SplitOperation', name: node.name, metadata: node.metadata, - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } function generateSelections( + schema: Schema, selections: $ReadOnlyArray, ): $ReadOnlyArray { const normalizationSelections: Array = []; selections.forEach(selection => { switch (selection.kind) { case 'Condition': - normalizationSelections.push(generateCondition(selection)); + normalizationSelections.push(generateCondition(schema, selection)); break; case 'ClientExtension': - normalizationSelections.push(generateClientExtension(selection)); + normalizationSelections.push( + generateClientExtension(schema, selection), + ); break; case 'ScalarField': normalizationSelections.push(...generateScalarField(selection)); @@ -110,22 +128,24 @@ function generateSelections( normalizationSelections.push(generateModuleImport(selection)); break; case 'InlineFragment': - normalizationSelections.push(generateInlineFragment(selection)); + normalizationSelections.push(generateInlineFragment(schema, selection)); break; case 'LinkedField': - normalizationSelections.push(...generateLinkedField(selection)); + normalizationSelections.push(...generateLinkedField(schema, selection)); break; case 'ConnectionField': - normalizationSelections.push(...generateConnectionField(selection)); + normalizationSelections.push( + ...generateConnectionField(schema, selection), + ); break; case 'Connection': - normalizationSelections.push(generateConnection(selection)); + normalizationSelections.push(generateConnection(schema, selection)); break; case 'Defer': - normalizationSelections.push(generateDefer(selection)); + normalizationSelections.push(generateDefer(schema, selection)); break; case 'Stream': - normalizationSelections.push(generateStream(selection)); + normalizationSelections.push(generateStream(schema, selection)); break; case 'InlineDataFragmentSpread': case 'FragmentSpread': @@ -142,28 +162,33 @@ function generateSelections( } function generateArgumentDefinitions( - nodes, + schema: Schema, + nodes: $ReadOnlyArray, ): $ReadOnlyArray { return nodes.map(node => { return { kind: 'LocalArgument', name: node.name, - type: node.type.toString(), + type: schema.getTypeString(node.type), defaultValue: node.defaultValue, }; }); } function generateClientExtension( + schema: Schema, node: ClientExtension, ): NormalizationSelection { return { kind: 'ClientExtension', - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } -function generateCondition(node, key): NormalizationSelection { +function generateCondition( + schema: Schema, + node: Condition, +): NormalizationSelection { if (node.condition.kind !== 'Variable') { throw createCompilerError( "NormalizationCodeGenerator: Expected 'Condition' with static " + @@ -175,11 +200,11 @@ function generateCondition(node, key): NormalizationSelection { kind: 'Condition', passingValue: node.passingValue, condition: node.condition.variableName, - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } -function generateDefer(node, key): NormalizationDefer { +function generateDefer(schema: Schema, node: Defer): NormalizationDefer { if ( !( node.if == null || @@ -201,19 +226,25 @@ function generateDefer(node, key): NormalizationDefer { kind: 'Defer', label: node.label, metadata: node.metadata, - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } -function generateInlineFragment(node): NormalizationSelection { +function generateInlineFragment( + schema: Schema, + node: InlineFragment, +): NormalizationSelection { return { kind: 'InlineFragment', - type: node.typeCondition.toString(), - selections: generateSelections(node.selections), + type: schema.getTypeString(node.typeCondition), + selections: generateSelections(schema, node.selections), }; } -function generateLinkedField(node): $ReadOnlyArray { +function generateLinkedField( + schema: Schema, + node: LinkedField, +): $ReadOnlyArray { // Note: it is important that the arguments of this field be sorted to // ensure stable generation of storage keys for equivalent arguments // which may have originally appeared in different orders across an app. @@ -247,16 +278,18 @@ function generateLinkedField(node): $ReadOnlyArray { return handleNode; })) || []; - const type = getRawType(node.type); + const type = schema.getRawType(node.type); let field: NormalizationLinkedField = { kind: 'LinkedField', alias: node.alias === node.name ? null : node.alias, name: node.name, storageKey: null, args: generateArgs(node.args), - concreteType: !isAbstractType(type) ? type.toString() : null, - plural: isPlural(node.type), - selections: generateSelections(node.selections), + concreteType: !schema.isAbstractType(type) + ? schema.getTypeString(type) + : null, + plural: isPlural(schema, node.type), + selections: generateSelections(schema, node.selections), }; // Precompute storageKey if possible const storageKey = getStaticStorageKey(field, node.metadata); @@ -266,10 +299,20 @@ function generateLinkedField(node): $ReadOnlyArray { return [field].concat(handles); } -function generateConnectionField(node): $ReadOnlyArray { - return generateLinkedField({ - ...node, +function generateConnectionField( + schema: Schema, + node: ConnectionField, +): $ReadOnlyArray { + return generateLinkedField(schema, { + name: node.name, + alias: node.alias, + loc: node.loc, + directives: node.directives, + metadata: node.metadata, + selections: node.selections, + type: node.type, handles: null, + connection: false, // this is only on the linked fields with @conneciton args: node.args.filter( arg => !ConnectionInterface.isConnectionCall({name: arg.name, value: null}), @@ -278,9 +321,12 @@ function generateConnectionField(node): $ReadOnlyArray { }); } -function generateConnection(node): NormalizationConnection { +function generateConnection( + schema: Schema, + node: Connection, +): NormalizationConnection { const {EDGES, PAGE_INFO} = ConnectionInterface.get(); - const selections = generateSelections(node.selections); + const selections = generateSelections(schema, node.selections); let edges: ?NormalizationLinkedField; let pageInfo: ?NormalizationLinkedField; selections.forEach(selection => { @@ -414,7 +460,7 @@ function generateScalarField(node): Array { return [field].concat(handles); } -function generateStream(node, key): NormalizationStream { +function generateStream(schema: Schema, node: Stream): NormalizationStream { if ( !( node.if == null || @@ -436,7 +482,7 @@ function generateStream(node, key): NormalizationStream { kind: 'Stream', label: node.label, metadata: node.metadata, - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } @@ -468,10 +514,6 @@ function generateArgumentValue( } } -function isPlural(type: $FlowFixMe): boolean { - return getNullableType(type) instanceof GraphQLList; -} - function generateArgs( args: $ReadOnlyArray, ): ?$ReadOnlyArray { @@ -514,4 +556,8 @@ function getStaticStorageKey( return getStorageKey(field, {}); } +function isPlural(schema: Schema, type: TypeID): boolean { + return schema.isList(schema.getNullableType(type)); +} + module.exports = {generate}; diff --git a/packages/relay-compiler/codegen/ReaderCodeGenerator.js b/packages/relay-compiler/codegen/ReaderCodeGenerator.js index 0dc376028027e..71f492ead5d6b 100644 --- a/packages/relay-compiler/codegen/ReaderCodeGenerator.js +++ b/packages/relay-compiler/codegen/ReaderCodeGenerator.js @@ -11,13 +11,11 @@ 'use strict'; const CodeMarker = require('../util/CodeMarker'); -const SchemaUtils = require('../core/GraphQLSchemaUtils'); const { createCompilerError, createUserError, } = require('../core/RelayCompilerError'); -const {GraphQLList} = require('graphql'); const { ConnectionInterface, getStorageKey, @@ -26,11 +24,22 @@ const { import type { Argument, + ArgumentDefinition, ClientExtension, Metadata, Fragment, Selection, + Condition, + LinkedField, + ScalarField, + FragmentSpread, + InlineFragment, + ModuleImport, + Connection, + ConnectionField, + InlineDataFragmentSpread, } from '../core/GraphQLIR'; +import type {Schema, TypeID} from '../core/Schema'; import type { ReaderArgument, ReaderArgumentDefinition, @@ -43,7 +52,6 @@ import type { ReaderScalarField, ReaderSelection, } from 'relay-runtime'; -const {getRawType, isAbstractType, getNullableType} = SchemaUtils; /** * @public @@ -51,7 +59,7 @@ const {getRawType, isAbstractType, getNullableType} = SchemaUtils; * Converts a GraphQLIR node into a plain JS object representation that can be * used at runtime. */ -function generate(node: Fragment): ReaderFragment { +function generate(schema: Schema, node: Fragment): ReaderFragment { if (node == null) { return node; } @@ -86,40 +94,44 @@ function generate(node: Fragment): ReaderFragment { return { kind: 'Fragment', name: node.name, - type: node.type.toString(), + type: schema.getTypeString(node.type), // $FlowFixMe metadata, - argumentDefinitions: generateArgumentDefinitions(node.argumentDefinitions), - selections: generateSelections(node.selections), + argumentDefinitions: generateArgumentDefinitions( + schema, + node.argumentDefinitions, + ), + selections: generateSelections(schema, node.selections), }; } function generateSelections( + schema: Schema, selections: $ReadOnlyArray, ): $ReadOnlyArray { return selections .map(selection => { switch (selection.kind) { case 'ClientExtension': - return generateClientExtension(selection); + return generateClientExtension(schema, selection); case 'FragmentSpread': - return generateFragmentSpread(selection); + return generateFragmentSpread(schema, selection); case 'Condition': - return generateCondition(selection); + return generateCondition(schema, selection); case 'ScalarField': - return generateScalarField(selection); + return generateScalarField(schema, selection); case 'ModuleImport': - return generateModuleImport(selection); + return generateModuleImport(schema, selection); case 'InlineDataFragmentSpread': - return generateInlineDataFragmentSpread(selection); + return generateInlineDataFragmentSpread(schema, selection); case 'InlineFragment': - return generateInlineFragment(selection); + return generateInlineFragment(schema, selection); case 'LinkedField': - return generateLinkedField(selection); + return generateLinkedField(schema, selection); case 'ConnectionField': - return generateConnectionField(selection); + return generateConnectionField(schema, selection); case 'Connection': - return generateConnection(selection); + return generateConnection(schema, selection); case 'Defer': case 'Stream': throw createCompilerError( @@ -135,7 +147,8 @@ function generateSelections( } function generateArgumentDefinitions( - nodes, + schema: Schema, + nodes: $ReadOnlyArray, ): $ReadOnlyArray { return nodes.map(node => { switch (node.kind) { @@ -143,30 +156,32 @@ function generateArgumentDefinitions( return { kind: 'LocalArgument', name: node.name, - type: node.type.toString(), + type: schema.getTypeString(node.type), defaultValue: node.defaultValue, }; case 'RootArgumentDefinition': return { kind: 'RootArgument', name: node.name, - type: node.type ? node.type.toString() : null, + type: node.type ? schema.getTypeString(node.type) : null, }; default: - (node: empty); throw new Error(); } }); } -function generateClientExtension(node: ClientExtension): ReaderSelection { +function generateClientExtension( + schema: Schema, + node: ClientExtension, +): ReaderSelection { return { kind: 'ClientExtension', - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } -function generateCondition(node): ReaderSelection { +function generateCondition(schema: Schema, node: Condition): ReaderSelection { if (node.condition.kind !== 'Variable') { throw createCompilerError( "ReaderCodeGenerator: Expected 'Condition' with static value to be " + @@ -178,11 +193,14 @@ function generateCondition(node): ReaderSelection { kind: 'Condition', passingValue: node.passingValue, condition: node.condition.variableName, - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } -function generateFragmentSpread(node): ReaderSelection { +function generateFragmentSpread( + schema: Schema, + node: FragmentSpread, +): ReaderSelection { return { kind: 'FragmentSpread', name: node.name, @@ -190,25 +208,32 @@ function generateFragmentSpread(node): ReaderSelection { }; } -function generateInlineFragment(node): ReaderSelection { +function generateInlineFragment( + schema: Schema, + node: InlineFragment, +): ReaderSelection { return { kind: 'InlineFragment', - type: node.typeCondition.toString(), - selections: generateSelections(node.selections), + type: schema.getTypeString(node.typeCondition), + selections: generateSelections(schema, node.selections), }; } function generateInlineDataFragmentSpread( - node, + schema: Schema, + node: InlineDataFragmentSpread, ): ReaderInlineDataFragmentSpread { return { kind: 'InlineDataFragmentSpread', name: node.name, - selections: generateSelections(node.selections), + selections: generateSelections(schema, node.selections), }; } -function generateLinkedField(node): ReaderLinkedField { +function generateLinkedField( + schema: Schema, + node: LinkedField, +): ReaderLinkedField { // Note: it is important that the arguments of this field be sorted to // ensure stable generation of storage keys for equivalent arguments // which may have originally appeared in different orders across an app. @@ -221,17 +246,18 @@ function generateLinkedField(node): ReaderLinkedField { // node.handles == null, // 'ReaderCodeGenerator: unexpected handles', // ); - - const type = getRawType(node.type); + const rawType = schema.getRawType(node.type); let field: ReaderLinkedField = { kind: 'LinkedField', alias: node.alias === node.name ? null : node.alias, name: node.name, storageKey: null, args: generateArgs(node.args), - concreteType: !isAbstractType(type) ? type.toString() : null, - plural: isPlural(node.type), - selections: generateSelections(node.selections), + concreteType: !schema.isAbstractType(rawType) + ? schema.getTypeString(rawType) + : null, + plural: isPlural(schema, node.type), + selections: generateSelections(schema, node.selections), }; // Precompute storageKey if possible const storageKey = getStaticStorageKey(field, node.metadata); @@ -241,9 +267,19 @@ function generateLinkedField(node): ReaderLinkedField { return field; } -function generateConnectionField(node): ReaderLinkedField { - return generateLinkedField({ - ...node, +function generateConnectionField( + schema: Schema, + node: ConnectionField, +): ReaderLinkedField { + return generateLinkedField(schema, { + name: node.name, + alias: node.alias, + loc: node.loc, + directives: node.directives, + metadata: node.metadata, + selections: node.selections, + type: node.type, + connection: false, // this is only on the linked fields with @conneciton handles: null, args: node.args.filter( arg => @@ -253,9 +289,12 @@ function generateConnectionField(node): ReaderLinkedField { }); } -function generateConnection(node): ReaderConnection { +function generateConnection( + schema: Schema, + node: Connection, +): ReaderConnection { const {EDGES, PAGE_INFO} = ConnectionInterface.get(); - const selections = generateSelections(node.selections); + const selections = generateSelections(schema, node.selections); let edges: ?ReaderLinkedField; let pageInfo: ?ReaderLinkedField; selections.forEach(selection => { @@ -284,7 +323,10 @@ function generateConnection(node): ReaderConnection { }; } -function generateModuleImport(node): ReaderModuleImport { +function generateModuleImport( + schema: Schema, + node: ModuleImport, +): ReaderModuleImport { const fragmentName = node.name; const regExpMatch = fragmentName.match( /^([a-zA-Z][a-zA-Z0-9]*)(?:_([a-zA-Z][_a-zA-Z0-9]*))?$/, @@ -312,7 +354,10 @@ function generateModuleImport(node): ReaderModuleImport { }; } -function generateScalarField(node): ReaderScalarField { +function generateScalarField( + schema: Schema, + node: ScalarField, +): ReaderScalarField { // Note: it is important that the arguments of this field be sorted to // ensure stable generation of storage keys for equivalent arguments // which may have originally appeared in different orders across an app. @@ -367,10 +412,6 @@ function generateArgument(node: Argument): ReaderArgument | null { } } -function isPlural(type: any): boolean { - return getNullableType(type) instanceof GraphQLList; -} - function generateArgs( args: $ReadOnlyArray, ): ?$ReadOnlyArray { @@ -410,6 +451,10 @@ function getStaticStorageKey(field: ReaderField, metadata: Metadata): ?string { return getStorageKey(field, {}); } +function isPlural(schema: Schema, type: TypeID): boolean { + return schema.isList(schema.getNullableType(type)); +} + module.exports = { generate, }; diff --git a/packages/relay-compiler/codegen/RelayCodeGenerator.js b/packages/relay-compiler/codegen/RelayCodeGenerator.js index 2c6dbb8667c28..160c67fd48ed6 100644 --- a/packages/relay-compiler/codegen/RelayCodeGenerator.js +++ b/packages/relay-compiler/codegen/RelayCodeGenerator.js @@ -16,6 +16,7 @@ const ReaderCodeGenerator = require('./ReaderCodeGenerator'); const {createCompilerError} = require('../core/RelayCompilerError'); import type {Fragment, Request, SplitOperation} from '../core/GraphQLIR'; +import type {Schema} from '../core/Schema'; import type { ConcreteRequest, NormalizationSplitOperation, @@ -28,10 +29,16 @@ import type { * Converts a GraphQLIR node into a plain JS object representation that can be * used at runtime. */ -declare function generate(node: Fragment): ReaderFragment; -declare function generate(node: Request): ConcreteRequest; -declare function generate(node: SplitOperation): NormalizationSplitOperation; -function generate(node: Fragment | Request | SplitOperation): any { +declare function generate(schema: Schema, node: Fragment): ReaderFragment; +declare function generate(schema: Schema, node: Request): ConcreteRequest; +declare function generate( + schema: Schema, + node: SplitOperation, +): NormalizationSplitOperation; +function generate( + schema: Schema, + node: Fragment | Request | SplitOperation, +): any { switch (node.kind) { case 'Fragment': if (node.metadata?.inlineData === true) { @@ -40,12 +47,12 @@ function generate(node: Fragment | Request | SplitOperation): any { name: node.name, }; } - return ReaderCodeGenerator.generate(node); + return ReaderCodeGenerator.generate(schema, node); case 'Request': return { kind: 'Request', - fragment: ReaderCodeGenerator.generate(node.fragment), - operation: NormalizationCodeGenerator.generate(node.root), + fragment: ReaderCodeGenerator.generate(schema, node.fragment), + operation: NormalizationCodeGenerator.generate(schema, node.root), params: { operationKind: node.root.operation, name: node.name, @@ -55,7 +62,7 @@ function generate(node: Fragment | Request | SplitOperation): any { }, }; case 'SplitOperation': - return NormalizationCodeGenerator.generate(node); + return NormalizationCodeGenerator.generate(schema, node); } throw createCompilerError( `RelayCodeGenerator: Unknown AST kind '${node.kind}'.`, diff --git a/packages/relay-compiler/codegen/RelayFileWriter.js b/packages/relay-compiler/codegen/RelayFileWriter.js index 240ba88e3f9b7..3ad983838d069 100644 --- a/packages/relay-compiler/codegen/RelayFileWriter.js +++ b/packages/relay-compiler/codegen/RelayFileWriter.js @@ -11,13 +11,12 @@ 'use strict'; const ASTConvert = require('../core/ASTConvert'); +const CodegenDirectory = require('./CodegenDirectory'); const CompilerContext = require('../core/GraphQLCompilerContext'); const Profiler = require('../core/GraphQLCompilerProfiler'); const RelayParser = require('../core/RelayParser'); const RelayValidator = require('../core/RelayValidator'); -const SchemaUtils = require('../core/GraphQLSchemaUtils'); -const CodegenDirectory = require('./CodegenDirectory'); const compileRelayArtifacts = require('./compileRelayArtifacts'); const crypto = require('crypto'); const graphql = require('graphql'); @@ -29,9 +28,10 @@ const writeRelayGeneratedFile = require('./writeRelayGeneratedFile'); const { getReaderSourceDefinitionName, } = require('../core/GraphQLDerivedFromMetadata'); +const {isExecutableDefinitionAST} = require('../core/SchemaUtils'); const {Map: ImmutableMap} = require('immutable'); -import type {DocumentNode, GraphQLSchema, ValidationContext} from 'graphql'; +import type {Schema} from '../core/Schema'; import type { FormatModule, TypeGenerator, @@ -41,8 +41,7 @@ import type {GraphQLReporter as Reporter} from '../reporters/GraphQLReporter'; import type {Filesystem} from './CodegenDirectory'; import type {SourceControl} from './SourceControl'; import type {RelayCompilerTransforms} from './compileRelayArtifacts'; - -const {isExecutableDefinitionAST} = SchemaUtils; +import type {DocumentNode, ValidationContext} from 'graphql'; export type GenerateExtraFiles = ( getOutputDirectory: (path?: string) => CodegenDirectory, @@ -83,17 +82,16 @@ export type WriterConfig = { function compileAll({ baseDir, baseDocuments, - baseSchema, + schema, compilerTransforms, documents, extraValidationRules, reporter, - schemaExtensions, typeGenerator, }: {| baseDir: string, baseDocuments: $ReadOnlyArray, - baseSchema: GraphQLSchema, + schema: Schema, compilerTransforms: RelayCompilerTransforms, documents: $ReadOnlyArray, extraValidationRules?: { @@ -101,19 +99,8 @@ function compileAll({ LOCAL_RULES?: $ReadOnlyArray, }, reporter: Reporter, - schemaExtensions: $ReadOnlyArray, typeGenerator: TypeGenerator, |}) { - // Can't convert to IR unless the schema already has Relay-local extensions - const transformedSchema = ASTConvert.transformASTSchema( - baseSchema, - schemaExtensions, - ); - const extendedSchema = ASTConvert.extendASTSchema(transformedSchema, [ - ...baseDocuments, - ...documents, - ]); - // Verify using local and global rules, can run global verifications here // because all files are processed together let validationRules = [ @@ -129,17 +116,13 @@ function compileAll({ } const definitions = ASTConvert.convertASTDocumentsWithBase( - extendedSchema, + schema, baseDocuments, documents, validationRules, RelayParser.transform, ); - - const compilerContext = new CompilerContext( - baseSchema, - extendedSchema, - ).addAll(definitions); + const compilerContext = new CompilerContext(schema).addAll(definitions); const transformedTypeContext = compilerContext.applyTransforms( typeGenerator.transforms, @@ -171,7 +154,7 @@ function writeAll({ onlyValidate, baseDocuments, documents, - schema: baseSchema, + schema, reporter, sourceControl, }: {| @@ -179,7 +162,7 @@ function writeAll({ onlyValidate: boolean, baseDocuments: ImmutableMap, documents: ImmutableMap, - schema: GraphQLSchema, + schema: Schema, reporter: Reporter, sourceControl: ?SourceControl, |}): Promise> { @@ -190,14 +173,13 @@ function writeAll({ transformedTypeContext, transformedQueryContext, } = compileAll({ + schema, baseDir: writerConfig.baseDir, baseDocuments: baseDocuments.valueSeq().toArray(), - baseSchema, compilerTransforms: writerConfig.compilerTransforms, documents: documents.valueSeq().toArray(), extraValidationRules: writerConfig.validationRules, reporter, - schemaExtensions: writerConfig.schemaExtensions, typeGenerator: writerConfig.typeGenerator, }); // Build a context from all the documents @@ -336,6 +318,7 @@ function writeAll({ ); await writeRelayGeneratedFile( + schema, getGeneratedDirectory(nodeName), definition, node, diff --git a/packages/relay-compiler/codegen/__tests__/compileRelayArtifacts-test.js b/packages/relay-compiler/codegen/__tests__/compileRelayArtifacts-test.js index 1d29a1aea48f2..0f50612caab51 100644 --- a/packages/relay-compiler/codegen/__tests__/compileRelayArtifacts-test.js +++ b/packages/relay-compiler/codegen/__tests__/compileRelayArtifacts-test.js @@ -16,6 +16,7 @@ const CodeMarker = require('../../util/CodeMarker'); const CompilerContext = require('../../core/GraphQLCompilerContext'); const RelayIRTransforms = require('../../core/RelayIRTransforms'); const RelayIRValidations = require('../../core/RelayIRValidations'); +const Schema = require('../../core/Schema'); const compileRelayArtifacts = require('../compileRelayArtifacts'); @@ -43,10 +44,9 @@ describe('compileRelayArtifacts', () => { RelayIRTransforms.schemaExtensions, ); const {definitions, schema} = parseGraphQLText(relaySchema, text); - // $FlowFixMe - const compilerContext = new CompilerContext(TestSchema, schema).addAll( - definitions, - ); + const compilerContext = new CompilerContext( + Schema.DEPRECATED__create(TestSchema, schema), + ).addAll(definitions); return compileRelayArtifacts( compilerContext, RelayIRTransforms, @@ -71,8 +71,7 @@ describe('compileRelayArtifacts', () => { function stringifyAST(ast: mixed): string { return CodeMarker.postProcess( - // $FlowFixMe(>=0.95.0) JSON.stringify can return undefined - JSON.stringify(ast, null, 2), + JSON.stringify(ast, null, 2) ?? 'null', moduleName => `require('${moduleName}')`, ); } diff --git a/packages/relay-compiler/codegen/compileRelayArtifacts.js b/packages/relay-compiler/codegen/compileRelayArtifacts.js index 6fec206dc3915..8ad2f90cef820 100644 --- a/packages/relay-compiler/codegen/compileRelayArtifacts.js +++ b/packages/relay-compiler/codegen/compileRelayArtifacts.js @@ -99,6 +99,7 @@ function compile( codeGenContext: CompilerContext, ): $ReadOnlyArray<[GeneratedDefinition, GeneratedNode]> { const results = []; + const schema = context.getSchema(); // Add everything from codeGenContext, these are the operations as well as // SplitOperations from @match. @@ -124,16 +125,16 @@ function compile( root: node, text: printOperation(printContext, fragment.name), }; - results.push([request, RelayCodeGenerator.generate(request)]); + results.push([request, RelayCodeGenerator.generate(schema, request)]); } else { - results.push([node, RelayCodeGenerator.generate(node)]); + results.push([node, RelayCodeGenerator.generate(schema, node)]); } } // Add all the Fragments from the fragmentContext for the reader ASTs. for (const node of fragmentContext.documents()) { if (node.kind === 'Fragment') { - results.push([node, RelayCodeGenerator.generate(node)]); + results.push([node, RelayCodeGenerator.generate(schema, node)]); } } return results; @@ -143,7 +144,7 @@ function printOperation(printContext: CompilerContext, name: string): string { const printableRoot = printContext.getRoot(name); return filterContextForNode(printableRoot, printContext) .documents() - .map(Printer.print) + .map(doc => Printer.print(printContext.getSchema(), doc)) .join('\n'); } diff --git a/packages/relay-compiler/codegen/writeRelayGeneratedFile.js b/packages/relay-compiler/codegen/writeRelayGeneratedFile.js index 930ace14c917b..7aff1059fe231 100644 --- a/packages/relay-compiler/codegen/writeRelayGeneratedFile.js +++ b/packages/relay-compiler/codegen/writeRelayGeneratedFile.js @@ -20,6 +20,7 @@ const invariant = require('invariant'); const {RelayConcreteNode} = require('relay-runtime'); import type {GeneratedDefinition} from '../core/GraphQLIR'; +import type {Schema} from '../core/Schema'; import type {FormatModule} from '../language/RelayLanguagePluginInterface'; import type CodegenDirectory from './CodegenDirectory'; import type {GeneratedNode} from 'relay-runtime'; @@ -47,6 +48,7 @@ function getConcreteType(node: GeneratedNode): string { } async function writeRelayGeneratedFile( + schema: Schema, codegenDir: CodegenDirectory, definition: GeneratedDefinition, _generatedNode: GeneratedNode, @@ -149,6 +151,7 @@ async function writeRelayGeneratedFile( ), sourceHash, node: generatedNode, + schema, }); codegenDir.writeFile(filename, moduleText, shouldRepersist); return generatedNode; diff --git a/packages/relay-compiler/core/ASTConvert.js b/packages/relay-compiler/core/ASTConvert.js index fb46898eae71b..5fc3beba764c9 100644 --- a/packages/relay-compiler/core/ASTConvert.js +++ b/packages/relay-compiler/core/ASTConvert.js @@ -16,10 +16,11 @@ const RelayValidator = require('./RelayValidator'); const { isExecutableDefinitionAST, isSchemaDefinitionAST, -} = require('./GraphQLSchemaUtils'); +} = require('./SchemaUtils'); const {extendSchema, parse, print, visit} = require('graphql'); import type {Fragment, Root} from './GraphQLIR'; +import type {Schema} from './Schema'; import type { DefinitionNode, DocumentNode, @@ -34,12 +35,12 @@ import type { type ASTDefinitionNode = FragmentDefinitionNode | OperationDefinitionNode; type TransformFn = ( - schema: GraphQLSchema, + schema: Schema, definitions: $ReadOnlyArray, ) => $ReadOnlyArray; function convertASTDocuments( - schema: GraphQLSchema, + schema: Schema, documents: $ReadOnlyArray, validationRules: $ReadOnlyArray, transform: TransformFn, @@ -66,7 +67,7 @@ function convertASTDocuments( } function convertASTDocumentsWithBase( - schema: GraphQLSchema, + schema: Schema, baseDocuments: $ReadOnlyArray, documents: $ReadOnlyArray, validationRules: $ReadOnlyArray, @@ -136,7 +137,7 @@ function convertASTDocumentsWithBase( } function convertASTDefinitions( - schema: GraphQLSchema, + schema: Schema, definitions: $ReadOnlyArray, validationRules: $ReadOnlyArray, transform: TransformFn, @@ -153,7 +154,7 @@ function convertASTDefinitions( definitions: operationDefinitions, }; // Will throw an error if there are validation issues - RelayValidator.validate(validationAST, schema, validationRules); + RelayValidator.validate(schema, validationAST, validationRules); return transform(schema, operationDefinitions); } diff --git a/packages/relay-compiler/core/GraphQLCompilerContext.js b/packages/relay-compiler/core/GraphQLCompilerContext.js index eb9f92975a6d8..afec4865dcf81 100644 --- a/packages/relay-compiler/core/GraphQLCompilerContext.js +++ b/packages/relay-compiler/core/GraphQLCompilerContext.js @@ -20,7 +20,7 @@ const {OrderedMap: ImmutableOrderedMap} = require('immutable'); import type {GraphQLReporter} from '../reporters/GraphQLReporter'; import type {Fragment, Location, Root, SplitOperation} from './GraphQLIR'; -import type {GraphQLSchema} from 'graphql'; +import type {Schema} from './Schema'; export type IRTransform = GraphQLCompilerContext => GraphQLCompilerContext; export type IRValidation = GraphQLCompilerContext => void; @@ -35,16 +35,13 @@ class GraphQLCompilerContext { _isMutable: boolean; _documents: ImmutableOrderedMap; _withTransform: WeakMap; - +serverSchema: GraphQLSchema; - +clientSchema: GraphQLSchema; + +_schema: Schema; - constructor(serverSchema: GraphQLSchema, clientSchema?: GraphQLSchema) { + constructor(schema: Schema) { this._isMutable = false; this._documents = new ImmutableOrderedMap(); this._withTransform = new WeakMap(); - this.serverSchema = serverSchema; - // If a separate client schema doesn't exist, use the server schema. - this.clientSchema = clientSchema || serverSchema; + this._schema = schema; } /** @@ -201,10 +198,14 @@ class GraphQLCompilerContext { ): GraphQLCompilerContext { const context = this._isMutable ? this - : new GraphQLCompilerContext(this.serverSchema, this.clientSchema); + : new GraphQLCompilerContext(this.getSchema()); context._documents = documents; return context; } + + getSchema(): Schema { + return this._schema; + } } module.exports = GraphQLCompilerContext; diff --git a/packages/relay-compiler/core/GraphQLIR.js b/packages/relay-compiler/core/GraphQLIR.js index a828e8ce43650..c7ec39b443636 100644 --- a/packages/relay-compiler/core/GraphQLIR.js +++ b/packages/relay-compiler/core/GraphQLIR.js @@ -10,15 +10,8 @@ 'use strict'; -import type { - GraphQLCompositeType, - GraphQLOutputType, - GraphQLInputType, - GraphQLLeafType, - GraphQLList, - GraphQLNonNull, - Source, -} from 'graphql'; +import type {TypeID} from './Schema'; +import type {Source} from 'graphql'; export type Metadata = ?{[key: string]: mixed}; @@ -47,7 +40,7 @@ export type Argument = {| +kind: 'Argument', +loc: Location, +name: string, - +type: ?GraphQLInputType, + +type: ?TypeID, +value: ArgumentValue, |}; @@ -82,7 +75,7 @@ export type Fragment = {| +metadata: Metadata, +name: string, +selections: $ReadOnlyArray, - +type: GraphQLCompositeType, + +type: TypeID, |}; export type FragmentSpread = {| @@ -97,7 +90,9 @@ export type FragmentSpread = {| export type Defer = {| +kind: 'Defer', +loc: Location, - +metadata: Metadata, + +metadata: ?{| + +fragmentTypeCondition: TypeID, + |}, +selections: $ReadOnlyArray, +label: string, +if: ArgumentValue | null, @@ -152,7 +147,7 @@ export type RootArgumentDefinition = {| +kind: 'RootArgumentDefinition', +loc: Location, +name: string, - +type: GraphQLInputType, + +type: TypeID, |}; export type InlineFragment = {| @@ -161,7 +156,7 @@ export type InlineFragment = {| +loc: Location, +metadata: Metadata, +selections: $ReadOnlyArray, - +typeCondition: GraphQLCompositeType, + +typeCondition: TypeID, |}; export type Handle = {| @@ -192,7 +187,7 @@ export type Connection = {| +initialCount: ArgumentValue, +streamLabel: string, |} | null, - +type: GraphQLOutputType, + +type: TypeID, |}; export type LinkedField = {| @@ -206,7 +201,7 @@ export type LinkedField = {| +metadata: Metadata, +name: string, +selections: $ReadOnlyArray, - +type: GraphQLOutputType, + +type: TypeID, |}; export type ConnectionField = {| @@ -218,7 +213,7 @@ export type ConnectionField = {| +metadata: Metadata, +name: string, +selections: $ReadOnlyArray, - +type: GraphQLOutputType, + +type: TypeID, |}; export type ListValue = {| @@ -238,7 +233,7 @@ export type LocalArgumentDefinition = {| +kind: 'LocalArgumentDefinition', +loc: Location, +name: string, - +type: GraphQLInputType, + +type: TypeID, |}; export type ModuleImport = {| @@ -307,14 +302,9 @@ export type Root = {| +name: string, +operation: 'query' | 'mutation' | 'subscription', +selections: $ReadOnlyArray, - +type: GraphQLCompositeType, + +type: TypeID, |}; -export type ScalarFieldType = - | GraphQLLeafType - | GraphQLList - | GraphQLNonNull>; - export type ScalarField = {| +alias: string, +args: $ReadOnlyArray, @@ -324,7 +314,7 @@ export type ScalarField = {| +loc: Location, +metadata: Metadata, +name: string, - +type: ScalarFieldType, + +type: TypeID, |}; export type Selection = @@ -351,12 +341,12 @@ export type SplitOperation = {| +loc: Location, +metadata: Metadata, +parentSources: Set, - +type: GraphQLCompositeType, + +type: TypeID, |}; export type Variable = {| +kind: 'Variable', +loc: Location, +variableName: string, - +type: ?GraphQLInputType, + +type: ?TypeID, |}; diff --git a/packages/relay-compiler/core/GraphQLIRPrinter.js b/packages/relay-compiler/core/GraphQLIRPrinter.js index e0405add4f353..4311546e09e45 100644 --- a/packages/relay-compiler/core/GraphQLIRPrinter.js +++ b/packages/relay-compiler/core/GraphQLIRPrinter.js @@ -13,15 +13,6 @@ const invariant = require('invariant'); const {DEFAULT_HANDLE_KEY} = require('../util/DefaultHandleKey'); -const { - GraphQLEnumType, - GraphQLID, - GraphQLInt, - GraphQLInputObjectType, - GraphQLList, - GraphQLNonNull, - GraphQLScalarType, -} = require('graphql'); import type {CompilerContextDocument} from './GraphQLCompilerContext'; import type { @@ -34,7 +25,7 @@ import type { Node, Selection, } from './GraphQLIR'; -import type {GraphQLInputType} from 'graphql'; +import type {Schema, TypeID} from './Schema'; const INDENT = ' '; @@ -44,28 +35,28 @@ const INDENT = ' '; * variables or fragment spreads with arguments, transform the node * prior to printing. */ -function print(node: CompilerContextDocument): string { +function print(schema: Schema, node: CompilerContextDocument): string { switch (node.kind) { case 'Fragment': return ( - `fragment ${node.name} on ${String(node.type)}` + - printFragmentArgumentDefinitions(node.argumentDefinitions) + - printDirectives(node.directives) + - printSelections(node, '') + + `fragment ${node.name} on ${schema.getTypeString(node.type)}` + + printFragmentArgumentDefinitions(schema, node.argumentDefinitions) + + printDirectives(schema, node.directives) + + printSelections(schema, node, '', {}) + '\n' ); case 'Root': return ( `${node.operation} ${node.name}` + - printArgumentDefinitions(node.argumentDefinitions) + - printDirectives(node.directives) + - printSelections(node, '') + + printArgumentDefinitions(schema, node.argumentDefinitions) + + printDirectives(schema, node.directives) + + printSelections(schema, node, '', {}) + '\n' ); case 'SplitOperation': return ( - `SplitOperation ${node.name} on ${String(node.type)}` + - printSelections(node, '') + + `SplitOperation ${node.name} on ${schema.getTypeString(node.type)}` + + printSelections(schema, node, '', {}) + '\n' ); default: @@ -79,6 +70,7 @@ function print(node: CompilerContextDocument): string { } function printSelections( + schema: Schema, node: Node, indent: string, options?: { @@ -91,7 +83,7 @@ function printSelections( return ''; } const printed = selections.map(selection => - printSelection(selection, indent, options), + printSelection(schema, selection, indent, options), ); return printed.length ? ` {\n${indent + INDENT}${printed.join( @@ -104,6 +96,7 @@ function printSelections( * Prints a field without subselections. */ function printField( + schema: Schema, field: Field, options?: { parentDirectives?: string, @@ -117,14 +110,15 @@ function printField( (field.alias === field.name ? field.name : field.alias + ': ' + field.name) + - printArguments(field.args) + + printArguments(schema, field.args) + parentDirectives + - printDirectives(field.directives) + - printHandles(field) + printDirectives(schema, field.directives) + + printHandles(schema, field) ); } function printSelection( + schema: Schema, selection: Selection, indent: string, options?: { @@ -139,31 +133,35 @@ function printSelection( selection.kind === 'LinkedField' || selection.kind === 'ConnectionField' ) { - str = printField(selection, {parentDirectives, isClientExtension}); - str += printSelections(selection, indent + INDENT, {isClientExtension}); + str = printField(schema, selection, {parentDirectives, isClientExtension}); + str += printSelections(schema, selection, indent + INDENT, { + isClientExtension, + }); } else if ( selection.kind === 'ModuleImport' || selection.kind === 'Connection' ) { str = selection.selections .map(matchSelection => - printSelection(matchSelection, indent, { + printSelection(schema, matchSelection, indent, { parentDirectives, isClientExtension, }), ) .join('\n' + indent + INDENT); } else if (selection.kind === 'ScalarField') { - str = printField(selection, {parentDirectives, isClientExtension}); + str = printField(schema, selection, {parentDirectives, isClientExtension}); } else if (selection.kind === 'InlineFragment') { str = ''; if (isClientExtension) { str += '# '; } - str += '... on ' + selection.typeCondition.toString(); + str += '... on ' + schema.getTypeString(selection.typeCondition); str += parentDirectives; - str += printDirectives(selection.directives); - str += printSelections(selection, indent + INDENT, {isClientExtension}); + str += printDirectives(schema, selection.directives); + str += printSelections(schema, selection, indent + INDENT, { + isClientExtension, + }); } else if (selection.kind === 'FragmentSpread') { str = ''; if (isClientExtension) { @@ -171,16 +169,16 @@ function printSelection( } str += '...' + selection.name; str += parentDirectives; - str += printFragmentArguments(selection.args); - str += printDirectives(selection.directives); + str += printFragmentArguments(schema, selection.args); + str += printDirectives(schema, selection.directives); } else if (selection.kind === 'InlineDataFragmentSpread') { str = `# ${selection.name} @inline` + `\n${indent}${INDENT}...` + parentDirectives + - printSelections(selection, indent + INDENT); + printSelections(schema, selection, indent + INDENT, {}); } else if (selection.kind === 'Condition') { - const value = printValue(selection.condition); + const value = printValue(schema, selection.condition, null); // For Flow invariant( value != null, @@ -191,7 +189,7 @@ function printSelection( condStr += parentDirectives; // For multi-selection conditions, pushes the condition down to each const subSelections = selection.selections.map(sel => - printSelection(sel, indent, { + printSelection(schema, sel, indent, { parentDirectives: condStr, isClientExtension, }), @@ -200,16 +198,19 @@ function printSelection( } else if (selection.kind === 'Stream') { let streamStr = ` @stream(label: "${selection.label}"`; if (selection.if !== null) { - streamStr += `, if: ${printValue(selection.if) ?? ''}`; + streamStr += `, if: ${printValue(schema, selection.if, null) ?? ''}`; } if (selection.initialCount !== null) { - streamStr += `, initial_count: ${printValue(selection.initialCount) ?? - ''}`; + streamStr += `, initial_count: ${printValue( + schema, + selection.initialCount, + null, + ) ?? ''}`; } streamStr += ')'; streamStr += parentDirectives; const subSelections = selection.selections.map(sel => - printSelection(sel, indent, { + printSelection(schema, sel, indent, { parentDirectives: streamStr, isClientExtension, }), @@ -218,7 +219,7 @@ function printSelection( } else if (selection.kind === 'Defer') { let deferStr = ` @defer(label: "${selection.label}"`; if (selection.if !== null) { - deferStr += `, if: ${printValue(selection.if) ?? ''}`; + deferStr += `, if: ${printValue(schema, selection.if, null) ?? ''}`; } deferStr += ')'; deferStr += parentDirectives; @@ -230,7 +231,7 @@ function printSelection( ) ) { const subSelections = selection.selections.map(sel => - printSelection(sel, indent, { + printSelection(schema, sel, indent, { parentDirectives: deferStr, isClientExtension, }), @@ -242,12 +243,15 @@ function printSelection( selection.metadata.fragmentTypeCondition != null ) { str = - `... on ${String(selection.metadata.fragmentTypeCondition)}` + - deferStr; + `... on ${schema.getTypeString( + selection.metadata.fragmentTypeCondition, + )}` + deferStr; } else { str = '...' + deferStr; } - str += printSelections(selection, indent + INDENT, {isClientExtension}); + str += printSelections(schema, selection, indent + INDENT, { + isClientExtension, + }); } } else if (selection.kind === 'ClientExtension') { invariant( @@ -261,7 +265,7 @@ function printSelection( INDENT + selection.selections .map(sel => - printSelection(sel, indent, { + printSelection(schema, sel, indent, { parentDirectives, isClientExtension: true, }), @@ -279,12 +283,13 @@ function printSelection( } function printArgumentDefinitions( + schema: Schema, argumentDefinitions: $ReadOnlyArray, ): string { const printed = argumentDefinitions.map(def => { - let str = `$${def.name}: ${def.type.toString()}`; + let str = `$${def.name}: ${schema.getTypeString(def.type)}`; if (def.defaultValue != null) { - str += ' = ' + printLiteral(def.defaultValue, def.type); + str += ' = ' + printLiteral(schema, def.defaultValue, def.type); } return str; }); @@ -292,6 +297,7 @@ function printArgumentDefinitions( } function printFragmentArgumentDefinitions( + schema: Schema, argumentDefinitions: $ReadOnlyArray, ): string { let printed; @@ -300,9 +306,13 @@ function printFragmentArgumentDefinitions( return; } printed = printed || []; - let str = `${def.name}: {type: "${def.type.toString()}"`; + let str = `${def.name}: {type: "${schema.getTypeString(def.type)}"`; if (def.defaultValue != null) { - str += `, defaultValue: ${printLiteral(def.defaultValue, def.type)}`; + str += `, defaultValue: ${printLiteral( + schema, + def.defaultValue, + def.type, + )}`; } str += '}'; printed.push(str); @@ -312,7 +322,7 @@ function printFragmentArgumentDefinitions( : ''; } -function printHandles(field: Field): string { +function printHandles(schema: Schema, field: Field): string { if (!field.handles) { return ''; } @@ -329,25 +339,34 @@ function printHandles(field: Field): string { return printed.length ? ' ' + printed.join(' ') : ''; } -function printDirectives(directives: $ReadOnlyArray): string { +function printDirectives( + schema: Schema, + directives: $ReadOnlyArray, +): string { const printed = directives.map(directive => { - return '@' + directive.name + printArguments(directive.args); + return '@' + directive.name + printArguments(schema, directive.args); }); return printed.length ? ' ' + printed.join(' ') : ''; } -function printFragmentArguments(args: $ReadOnlyArray) { - const printedArgs = printArguments(args); +function printFragmentArguments( + schema: Schema, + args: $ReadOnlyArray, +) { + const printedArgs = printArguments(schema, args); if (!printedArgs.length) { return ''; } return ` @arguments${printedArgs}`; } -function printArguments(args: $ReadOnlyArray): string { +function printArguments( + schema: Schema, + args: $ReadOnlyArray, +): string { const printed = []; args.forEach(arg => { - const printedValue = printValue(arg.value, arg.type); + const printedValue = printValue(schema, arg.value, arg.type); if (printedValue != null) { printed.push(arg.name + ': ' + printedValue); } @@ -355,22 +374,32 @@ function printArguments(args: $ReadOnlyArray): string { return printed.length ? '(' + printed.join(', ') + ')' : ''; } -function printValue(value: ArgumentValue, type: ?GraphQLInputType): ?string { - if (type instanceof GraphQLNonNull) { - type = type.ofType; +function printValue( + schema: Schema, + value: ArgumentValue, + type: ?TypeID, +): ?string { + if (type != null && schema.isNonNull(type)) { + type = schema.getNullableType(type); } if (value.kind === 'Variable') { return '$' + value.variableName; } else if (value.kind === 'ObjectValue') { invariant( - type instanceof GraphQLInputObjectType, + type && schema.isInput(type), 'GraphQLIRPrinter: Need an InputObject type to print objects.', ); - const typeFields = type.getFields(); const pairs = value.fields .map(field => { - const innerValue = printValue(field.value, typeFields[field.name].type); + const fieldConfig = + type != null + ? schema.hasField(type, field.name) + ? schema.getFieldConfig(schema.expectField(type, field.name)) + : null + : null; + const innerValue = + fieldConfig && printValue(schema, field.value, fieldConfig.type); return innerValue == null ? null : field.name + ': ' + innerValue; }) .filter(Boolean); @@ -378,28 +407,30 @@ function printValue(value: ArgumentValue, type: ?GraphQLInputType): ?string { return '{' + pairs.join(', ') + '}'; } else if (value.kind === 'ListValue') { invariant( - type instanceof GraphQLList, + type && schema.isList(type), 'GraphQLIRPrinter: Need a type in order to print arrays.', ); - const innerType = type.ofType; - return `[${value.items.map(i => printValue(i, innerType)).join(', ')}]`; + const innerType = schema.getNonListType(type); + return `[${value.items + .map(i => printValue(schema, i, innerType)) + .join(', ')}]`; } else if (value.value != null) { - return printLiteral(value.value, type); + return printLiteral(schema, value.value, type); } else { return null; } } -function printLiteral(value: mixed, type: ?GraphQLInputType): string { +function printLiteral(schema: Schema, value: mixed, type: ?TypeID): string { if (value == null) { - // $FlowFixMe(>=0.95.0) JSON.stringify can return undefined - return JSON.stringify(value); + return JSON.stringify(value) ?? 'null'; } - if (type instanceof GraphQLNonNull) { - type = type.ofType; + if (type != null && schema.isNonNull(type)) { + type = schema.getNullableType(type); } - if (type instanceof GraphQLEnumType) { - let result = type.serialize(value); + + if (type && schema.isEnum(type)) { + let result = schema.serialize(type, value); if (result == null && typeof value === 'string') { // For backwards compatibility, print invalid input values as-is. This // can occur with literals defined as an @argumentDefinitions @@ -409,49 +440,49 @@ function printLiteral(value: mixed, type: ?GraphQLInputType): string { invariant( typeof result === 'string', 'GraphQLIRPrinter: Expected value of type %s to be a valid enum value, got `%s`.', - type.name, - JSON.stringify(value), + schema.getTypeString(type), + JSON.stringify(value) ?? 'null', ); return result; - } else if (type === GraphQLID || type === GraphQLInt) { - // For backwards compatibility, print integer and ID values as-is - // $FlowFixMe(>=0.95.0) JSON.stringify can return undefined - return JSON.stringify(value); - } else if (type instanceof GraphQLScalarType) { - const result = type.serialize(value); - // $FlowFixMe(>=0.95.0) JSON.stringify can return undefined - return JSON.stringify(result); + } else if (type && (schema.isId(type) || schema.isInt(type))) { + return JSON.stringify(value) ?? ''; + } else if (type && schema.isScalar(type)) { + const result = schema.serialize(type, value); + return JSON.stringify(result) ?? ''; } else if (Array.isArray(value)) { invariant( - type instanceof GraphQLList, + type && schema.isList(type), 'GraphQLIRPrinter: Need a type in order to print arrays.', ); - const itemType = type.ofType; + const itemType = schema.getNonListType(type); return ( - '[' + value.map(item => printLiteral(item, itemType)).join(', ') + ']' + '[' + + value.map(item => printLiteral(schema, item, itemType)).join(', ') + + ']' ); - } else if (type instanceof GraphQLList && value != null) { + } else if (type && schema.isList(type) && value != null) { // Not an array, but still a list. Treat as list-of-one as per spec 3.1.7: // http://facebook.github.io/graphql/October2016/#sec-Lists - return printLiteral(value, type.ofType); + return printLiteral(schema, value, schema.getNonListType(type)); } else if (typeof value === 'object' && value != null) { const fields = []; invariant( - type instanceof GraphQLInputObjectType, + type && schema.isInput(type), 'GraphQLIRPrinter: Need an InputObject type to print objects.', ); - const typeFields = type.getFields(); for (const key in value) { if (value.hasOwnProperty(key)) { + const fieldConfig = schema.getFieldConfig( + schema.expectField(type, key), + ); fields.push( - key + ': ' + printLiteral(value[key], typeFields[key].type), + key + ': ' + printLiteral(schema, value[key], fieldConfig.type), ); } } return '{' + fields.join(', ') + '}'; } else { - // $FlowFixMe(>=0.95.0) JSON.stringify can return undefined - return JSON.stringify(value); + return JSON.stringify(value) ?? 'null'; } } diff --git a/packages/relay-compiler/core/GraphQLIRVisitor.js b/packages/relay-compiler/core/GraphQLIRVisitor.js index c78b96365a81e..b0a93fccb78ba 100644 --- a/packages/relay-compiler/core/GraphQLIRVisitor.js +++ b/packages/relay-compiler/core/GraphQLIRVisitor.js @@ -10,6 +10,8 @@ 'use strict'; +const {visit} = require('graphql'); + import type { Argument, ClientExtension, @@ -35,8 +37,6 @@ import type { Variable, } from './GraphQLIR'; -const visit = require('graphql').visit; - const NodeKeys = { Argument: ['value'], ClientExtension: ['selections'], diff --git a/packages/relay-compiler/core/GraphQLSchemaUtils.js b/packages/relay-compiler/core/GraphQLSchemaUtils.js deleted file mode 100644 index 51c1d3ef6df1f..0000000000000 --- a/packages/relay-compiler/core/GraphQLSchemaUtils.js +++ /dev/null @@ -1,296 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict - * @format - */ - -'use strict'; - -const invariant = require('invariant'); -const nullthrows = require('../util/nullthrowsOSS'); - -const { - GraphQLInterfaceType, - GraphQLList, - GraphQLObjectType, - GraphQLSchema, - GraphQLUnionType, - SchemaMetaFieldDef, - TypeMetaFieldDef, - TypeNameMetaFieldDef, - assertAbstractType, - getNamedType, - getNullableType, -} = require('graphql'); - -import type GraphQLCompilerContext from '../core/GraphQLCompilerContext'; -import type {Field, ScalarField} from '../core/GraphQLIR'; -import type { - ASTNode, - GraphQLCompositeType, - GraphQLEnumType, - GraphQLInputObjectType, - GraphQLLeafType, - GraphQLNamedType, - GraphQLNonNull, - GraphQLScalarType, - GraphQLType, -} from 'graphql'; - -const ID = 'id'; -const ID_TYPE = 'ID'; - -type GraphQLSingularBaseType = - | GraphQLScalarType - | GraphQLObjectType - | GraphQLInterfaceType - | GraphQLUnionType - | GraphQLEnumType - | GraphQLInputObjectType; - -type GraphQLSingularType = - | GraphQLSingularBaseType - | GraphQLNonNull; - -/** - * Determine if the given type may implement the named type: - * - it is the named type - * - it implements the named interface - * - it is an abstract type and *some* of its concrete types may - * implement the named type - */ -function mayImplement( - schema: GraphQLSchema, - type: GraphQLType, - typeName: string, -): boolean { - const unmodifiedType = getRawType(type); - return ( - unmodifiedType.toString() === typeName || - implementsInterface(unmodifiedType, typeName) || - (isAbstractType(unmodifiedType) && - hasConcreteTypeThatImplements(schema, unmodifiedType, typeName)) - ); -} - -function canHaveSelections(type: GraphQLType): boolean { - return ( - type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType - ); -} - -/** - * Implements duck typing that checks whether a type has an id field of the ID - * type. This is approximating what we can hopefully do with the __id proposal - * a bit more cleanly. - */ -function hasID(schema: GraphQLSchema, type: GraphQLCompositeType): boolean { - const unmodifiedType = getRawType(type); - invariant( - unmodifiedType instanceof GraphQLObjectType || - unmodifiedType instanceof GraphQLInterfaceType, - 'GraphQLSchemaUtils.hasID(): Expected a concrete type or interface, ' + - 'got type `%s`.', - type, - ); - const idType = schema.getType(ID_TYPE); - const idField = unmodifiedType.getFields()[ID]; - return idField && getRawType(idField.type) === idType; -} - -/** - * Determine if a type is abstract (not concrete). - * - * Note: This is used in place of the `graphql` version of the function in order - * to not break `instanceof` checks with Jest. This version also unwraps - * non-null/list wrapper types. - */ -function isAbstractType(type: GraphQLType): boolean { - const rawType = getRawType(type); - return ( - rawType instanceof GraphQLInterfaceType || - rawType instanceof GraphQLUnionType - ); -} - -function isUnionType(type: GraphQLType): boolean %checks { - return type instanceof GraphQLUnionType; -} - -/** - * Get the unmodified type, with list/null wrappers removed. - */ -function getRawType(type: GraphQLType): GraphQLNamedType { - return nullthrows(getNamedType(type)); -} - -/** - * Gets the non-list type, removing the list wrapper if present. - */ -function getSingularType(type: GraphQLType): GraphQLSingularType { - let unmodifiedType = type; - while (unmodifiedType instanceof GraphQLList) { - unmodifiedType = unmodifiedType.ofType; - } - return (unmodifiedType: $FlowFixMe); -} - -/** - * @public - */ -function implementsInterface( - type: GraphQLType, - interfaceName: string, -): boolean { - return getInterfaces(type).some( - interfaceType => interfaceType.toString() === interfaceName, - ); -} - -/** - * @private - */ -function hasConcreteTypeThatImplements( - schema: GraphQLSchema, - type: GraphQLType, - interfaceName: string, -): boolean { - return ( - isAbstractType(type) && - getConcreteTypes(schema, type).some(concreteType => - implementsInterface(concreteType, interfaceName), - ) - ); -} - -/** - * @private - */ -function getConcreteTypes( - schema: GraphQLSchema, - type: GraphQLType, -): $ReadOnlyArray { - return schema.getPossibleTypes(assertAbstractType(type)); -} - -/** - * @private - */ -function getInterfaces(type: GraphQLType): Array { - if (type instanceof GraphQLObjectType) { - return type.getInterfaces(); - } - return []; -} - -/** - * @public - * - * Determine if an AST node contains a fragment/operation definition. - */ -function isExecutableDefinitionAST(ast: ASTNode): boolean %checks { - return ( - ast.kind === 'FragmentDefinition' || ast.kind === 'OperationDefinition' - ); -} - -/** - * @public - * - * Determine if an AST node contains a schema definition. - */ -function isSchemaDefinitionAST(ast: ASTNode): boolean %checks { - return ( - ast.kind === 'SchemaDefinition' || - ast.kind === 'ScalarTypeDefinition' || - ast.kind === 'ObjectTypeDefinition' || - ast.kind === 'InterfaceTypeDefinition' || - ast.kind === 'UnionTypeDefinition' || - ast.kind === 'EnumTypeDefinition' || - ast.kind === 'InputObjectTypeDefinition' || - ast.kind === 'DirectiveDefinition' || - ast.kind === 'ScalarTypeExtension' || - ast.kind === 'ObjectTypeExtension' || - ast.kind === 'InterfaceTypeExtension' || - ast.kind === 'UnionTypeExtension' || - ast.kind === 'EnumTypeExtension' || - ast.kind === 'InputObjectTypeExtension' - ); -} - -function isServerDefinedField( - field: Field, - compilerContext: GraphQLCompilerContext, - parentType: GraphQLType, -): boolean { - const {serverSchema} = compilerContext; - const rawType = getRawType(field.type); - const serverType = serverSchema.getType(rawType.name); - const parentServerType = serverSchema.getType(getRawType(parentType).name); - return ( - (serverType != null && - parentServerType != null && - (canHaveSelections(parentType) && - assertTypeWithFields(parentType).getFields()[field.name] != null)) || - // Allow metadata fields and fields defined on classic "fat" interfaces - field.name === SchemaMetaFieldDef.name || - field.name === TypeMetaFieldDef.name || - field.name === TypeNameMetaFieldDef.name || - field.directives.some(({name}) => name === 'fixme_fat_interface') - ); -} - -function isClientDefinedField( - field: Field, - compilerContext: GraphQLCompilerContext, - parentType: GraphQLType, -): boolean { - return !isServerDefinedField(field, compilerContext, parentType); -} - -function assertTypeWithFields( - type: ?GraphQLType, -): GraphQLObjectType | GraphQLInterfaceType { - invariant( - type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType, - 'GraphQLSchemaUtils: Expected type `%s` to be an object or interface type.', - type, - ); - return type; -} - -function generateIDField(idType: GraphQLLeafType): ScalarField { - return { - kind: 'ScalarField', - alias: ID, - args: [], - directives: [], - handles: null, - loc: {kind: 'Generated'}, - metadata: null, - name: ID, - type: idType, - }; -} - -module.exports = { - assertTypeWithFields, - canHaveSelections, - generateIDField, - getNullableType, - getRawType, - getSingularType, - hasID, - implementsInterface, - isAbstractType, - isClientDefinedField, - isExecutableDefinitionAST, - isSchemaDefinitionAST, - isServerDefinedField, - isUnionType, - mayImplement, -}; diff --git a/packages/relay-compiler/core/RelayCompilerScope.js b/packages/relay-compiler/core/RelayCompilerScope.js index 8a6a5f6044901..57fd527461512 100644 --- a/packages/relay-compiler/core/RelayCompilerScope.js +++ b/packages/relay-compiler/core/RelayCompilerScope.js @@ -15,7 +15,6 @@ const { createUserError, eachWithErrors, } = require('./RelayCompilerError'); -const {GraphQLNonNull} = require('graphql'); import type { Argument, @@ -25,7 +24,7 @@ import type { LocalArgumentDefinition, Variable, } from './GraphQLIR'; - +import type {Schema} from './Schema'; /** * A scope is a mapping of the values for each argument defined by the nearest * ancestor root or fragment of a given IR selection. A scope maps argument @@ -136,6 +135,7 @@ function getRootScope( * } */ function getFragmentScope( + schema: Schema, definitions: $ReadOnlyArray, args: $ReadOnlyArray, parentScope: Scope, @@ -175,13 +175,15 @@ function getFragmentScope( // value. if ( definition.defaultValue == null && - definition.type instanceof GraphQLNonNull + schema.isNonNull(definition.type) ) { const argNode = args.find(a => a.name === definition.name); throw createUserError( - `No value found for required argument '${definition.name}: ${String( - definition.type, - )}' on fragment '${spread.name}'.`, + `No value found for required argument '${ + definition.name + }: ${schema.getTypeString(definition.type)}' on fragment '${ + spread.name + }'.`, [argNode?.loc ?? spread.loc], ); } diff --git a/packages/relay-compiler/core/RelayParser.js b/packages/relay-compiler/core/RelayParser.js index 00122f72d119c..8caaa1011e95f 100644 --- a/packages/relay-compiler/core/RelayParser.js +++ b/packages/relay-compiler/core/RelayParser.js @@ -15,38 +15,15 @@ const Profiler = require('./GraphQLCompilerProfiler'); const partitionArray = require('../util/partitionArray'); const {DEFAULT_HANDLE_KEY} = require('../util/DefaultHandleKey'); -const { - getNullableType, - isExecutableDefinitionAST, -} = require('./GraphQLSchemaUtils'); const { createCombinedError, createCompilerError, createUserError, eachWithErrors, } = require('./RelayCompilerError'); +const {isExecutableDefinitionAST} = require('./SchemaUtils'); const {getFieldDefinitionLegacy} = require('./getFieldDefinition'); -const { - assertCompositeType, - assertInputType, - assertOutputType, - extendSchema, - getNamedType, - GraphQLEnumType, - GraphQLID, - GraphQLInputObjectType, - GraphQLList, - GraphQLNonNull, - GraphQLScalarType, - isLeafType, - isType, - typeFromAST, - isTypeSubTypeOf, - parse: parseGraphQL, - parseType, - print, - Source, -} = require('graphql'); +const {parse: parseGraphQL, parseType, Source} = require('graphql'); import type { Argument, @@ -61,10 +38,10 @@ import type { LocalArgumentDefinition, Location, Root, - ScalarFieldType, Selection, Variable, } from './GraphQLIR'; +import type {Schema, TypeID, FieldArgument} from './Schema'; import type {GetFieldDefinitionFn} from './getFieldDefinition'; import type { ASTNode, @@ -78,11 +55,6 @@ import type { FloatValueNode, FragmentDefinitionNode, FragmentSpreadNode, - GraphQLArgument, - GraphQLInputType, - GraphQLOutputType, - GraphQLSchema, - GraphQLType, InlineFragmentNode, IntValueNode, ListValueNode, @@ -91,7 +63,6 @@ import type { OperationDefinitionNode, SelectionSetNode, StringValueNode, - TypeNode, ValueNode, VariableNode, } from 'graphql'; @@ -111,10 +82,15 @@ type VariableDefinition = {| ast: ASTNode, name: string, defaultValue: mixed, - type: GraphQLInputType, + type: TypeID, defined: boolean, |}; +type UnknownVariable = {| + ast: VariableNode, + type: ?TypeID, +|}; + const ARGUMENT_DEFINITIONS = 'argumentDefinitions'; const ARGUMENTS = 'arguments'; const DEPRECATED_UNCHECKED_ARGUMENTS = 'uncheckedArguments_DEPRECATED'; @@ -144,14 +120,15 @@ const IF = 'if'; * intermediate representation (IR). */ function parse( - schema: GraphQLSchema, + schema: Schema, text: string, filename?: string, ): $ReadOnlyArray { const ast = parseGraphQL(new Source(text, filename)); + // TODO T24511737 figure out if this is dangerous const parser = new RelayParser( - extendSchema(schema, ast, {assumeValid: true}), + schema.DEPRECATED__extend(ast), ast.definitions, ); return parser.transform(); @@ -162,7 +139,7 @@ function parse( * internal, strongly-typed intermediate representation (IR). */ function transform( - schema: GraphQLSchema, + schema: Schema, definitions: $ReadOnlyArray, ): $ReadOnlyArray { return Profiler.run('RelayParser.transform', () => { @@ -171,31 +148,15 @@ function transform( }); } -/** - * Helper for calling `typeFromAST()` with a clear warning when the type does - * not exist. This enables the pattern `assertXXXType(getTypeFromAST(...))`, - * emitting distinct errors for unknown types vs types of the wrong category. - */ -function getTypeFromAST(schema: GraphQLSchema, ast: TypeNode): GraphQLType { - const type = typeFromAST(schema, ast); - if (!isType(type)) { - throw createUserError(`Unknown type: '${print(ast)}'.`, null, [ast]); - } - return (type: $FlowFixMe); -} - /** * @private */ class RelayParser { _definitions: Map; _getFieldDefinition: GetFieldDefinitionFn; - _schema: GraphQLSchema; + +_schema: Schema; - constructor( - schema: GraphQLSchema, - definitions: $ReadOnlyArray, - ) { + constructor(schema: Schema, definitions: $ReadOnlyArray) { this._definitions = new Map(); // leaving this configurable to make it easy to experiment w changing later this._getFieldDefinition = getFieldDefinitionLegacy; @@ -213,7 +174,7 @@ class RelayParser { } }); if (duplicated.size) { - throw new Error( + throw createUserError( 'RelayParser: Encountered duplicate definitions for one or more ' + 'documents: each document must have a unique name. Duplicated documents:\n' + Array.from(duplicated, name => `- ${name}`).join('\n'), @@ -356,15 +317,24 @@ class RelayParser { } const typeAST = parseType(typeString); - const type = assertInputType(getTypeFromAST(this._schema, typeAST)); + const type = this._schema.assertInputType( + this._schema.expectTypeFromAST( + typeAST, + `Unknown type "${typeString}" referenced in the argument definitions.`, + null, + [arg], + ), + ); + const defaultValue = defaultValueNode != null ? transformValue( + this._schema, defaultValueNode, type, variableAst => { throw createUserError( - "Expected 'defaultValue' to be a literal, got a variable.", + "RelayParser: Expected 'defaultValue' to be a literal, got a variable.", null, [variableAst], ); @@ -396,10 +366,12 @@ class RelayParser { _buildOperationArgumentDefinitions( operation: OperationDefinitionNode, ): VariableDefinitions { + const schema = this._schema; const variableDefinitions = new Map(); (operation.variableDefinitions || []).forEach(def => { const name = getName(def.variable); - const type = assertInputType(getTypeFromAST(this._schema, def.type)); + const type = schema.assertInputType(schema.expectTypeFromAST(def.type)); + const defaultValue = def.defaultValue ? transformLiteralValue(def.defaultValue, def) : null; @@ -427,7 +399,7 @@ class RelayParser { * @private */ function parseDefinition( - schema: GraphQLSchema, + schema: Schema, getFieldDefinition: GetFieldDefinitionFn, entries: Map< string, @@ -459,13 +431,13 @@ class GraphQLDefinitionParser { |}, >; _getFieldDefinition: GetFieldDefinitionFn; - _schema: GraphQLSchema; + _schema: Schema; _variableDefinitions: VariableDefinitions; - _unknownVariables: Map; + _unknownVariables: Map; _directiveLocations: ?Map>; constructor( - schema: GraphQLSchema, + schema: Schema, getFieldDefinition: GetFieldDefinitionFn, entries: Map< string, @@ -504,7 +476,7 @@ class GraphQLDefinitionParser { _recordAndVerifyVariableReference( variable: VariableNode, name: string, - usedAsType: ?GraphQLInputType, + usedAsType: ?TypeID, ): void { // Special case for variables used in @arguments where we currently // aren't guaranteed to be able to resolve the type. @@ -527,9 +499,12 @@ class GraphQLDefinitionParser { if (variableDefinition.defaultValue != null) { // If a default value is defined then it is guaranteed to be used // at runtime such that the effective type of the variable is non-null - effectiveType = new GraphQLNonNull(getNullableType(effectiveType)); + effectiveType = this._schema.getNonNullType( + this._schema.getNullableType(effectiveType), + ); } - if (!isTypeSubTypeOf(this._schema, effectiveType, usedAsType)) { + + if (!this._schema.isTypeSubTypeOf(effectiveType, usedAsType)) { throw createUserError( `Variable '\$${name}' was defined as type '${String( variableDefinition.type, @@ -550,11 +525,11 @@ class GraphQLDefinitionParser { type: usedAsType, }); } else { - const {type: previousType, ast: previousVariable} = previous; + const {ast: previousVariable, type: previousType} = previous; if ( !( - isTypeSubTypeOf(this._schema, usedAsType, previousType) || - isTypeSubTypeOf(this._schema, previousType, usedAsType) + this._schema.isTypeSubTypeOf(usedAsType, previousType) || + this._schema.isTypeSubTypeOf(previousType, usedAsType) ) ) { throw createUserError( @@ -568,7 +543,7 @@ class GraphQLDefinitionParser { // If the new used type has stronger requirements, use that type as reference, // otherwise keep referencing the previous type - if (isTypeSubTypeOf(this._schema, usedAsType, previousType)) { + if (this._schema.isTypeSubTypeOf(usedAsType, previousType)) { this._unknownVariables.set(name, { ast: variable, type: usedAsType, @@ -629,9 +604,17 @@ class GraphQLDefinitionParser { ), 'FRAGMENT_DEFINITION', ); - const type = assertCompositeType( - getTypeFromAST(this._schema, fragment.typeCondition), + const type = this._schema.assertCompositeType( + this._schema.expectTypeFromAST( + fragment.typeCondition, + `Unknown type '${ + fragment.typeCondition.name.value + }' on the fragment definition '${fragment.name.value}'.`, + null, + [fragment.typeCondition], + ), ); + const selections = this._transformSelections(fragment.selectionSet, type); const argumentDefinitions = [ ...buildArgumentDefinitions(this._variableDefinitions), @@ -641,7 +624,6 @@ class GraphQLDefinitionParser { kind: 'RootArgumentDefinition', loc: buildLocation(variableReference.ast.loc), name, - // $FlowFixMe - could be null type: variableReference.type, }); } @@ -653,6 +635,7 @@ class GraphQLDefinitionParser { name: getName(fragment), selections, type, + // $FlowFixMe - could be null argumentDefinitions, }; } @@ -683,20 +666,21 @@ class GraphQLDefinitionParser { definition.directives || [], this._getLocationFromOperation(definition), ); - let type; + let type: TypeID; let operation; + const schema = this._schema; switch (definition.operation) { case 'query': operation = 'query'; - type = assertCompositeType(this._schema.getQueryType()); + type = schema.expectQueryType(null, null, [definition]); break; case 'mutation': operation = 'mutation'; - type = assertCompositeType(this._schema.getMutationType()); + type = schema.expectMutationType(null, null, [definition]); break; case 'subscription': operation = 'subscription'; - type = assertCompositeType(this._schema.getSubscriptionType()); + type = schema.expectSubscriptionType(null, null, [definition]); break; default: (definition.operation: empty); @@ -740,7 +724,7 @@ class GraphQLDefinitionParser { _transformSelections( selectionSet: SelectionSetNode, - parentType: GraphQLOutputType, + parentType: TypeID, ): $ReadOnlyArray { return selectionSet.selections.map(selection => { let node; @@ -775,11 +759,12 @@ class GraphQLDefinitionParser { _transformInlineFragment( fragment: InlineFragmentNode, - parentType: GraphQLOutputType, + parentType: TypeID, ): InlineFragment { - const typeCondition = assertCompositeType( - fragment.typeCondition - ? getTypeFromAST(this._schema, fragment.typeCondition) + const schema = this._schema; + const typeCondition = schema.assertCompositeType( + fragment.typeCondition != null + ? schema.expectTypeFromAST(fragment.typeCondition) : parentType, ); const directives = this._transformDirectives( @@ -802,7 +787,7 @@ class GraphQLDefinitionParser { _transformFragmentSpread( fragmentSpread: FragmentSpreadNode, - parentType: GraphQLOutputType, + parentType: TypeID, ): FragmentSpread { const fragmentName = getName(fragmentSpread); const [argumentDirectives, otherDirectives] = partitionArray( @@ -900,32 +885,33 @@ class GraphQLDefinitionParser { }; } - _transformField(field: FieldNode, parentType: GraphQLOutputType): Field { + _transformField(field: FieldNode, parentType: TypeID): Field { + const schema = this._schema; const name = getName(field); - const fieldDef = this._getFieldDefinition( - this._schema, - parentType, - name, - field, - ); - + const fieldDef = this._getFieldDefinition(schema, parentType, name, field); if (fieldDef == null) { throw createUserError( - `Unknown field '${name}' on type '${String(parentType)}'.`, + `Unknown field '${name}' on type '${schema.getTypeString( + parentType, + )}'.`, null, [field], ); } const alias = field.alias?.value ?? name; - const args = this._transformArguments(field.arguments || [], fieldDef.args); + const args = this._transformArguments( + field.arguments || [], + schema.getFieldArgs(fieldDef), + ); const [otherDirectives, clientFieldDirectives] = partitionArray( field.directives || [], directive => getName(directive) !== CLIENT_FIELD, ); const directives = this._transformDirectives(otherDirectives, 'FIELD'); - const type = assertOutputType(fieldDef.type); + const type = schema.assertOutputType(schema.getFieldType(fieldDef)); + const handles = this._transformHandle(name, args, clientFieldDirectives); - if (isLeafType(getNamedType(type))) { + if (schema.isLeafType(schema.getRawType(type))) { if ( field.selectionSet && field.selectionSet.selections && @@ -946,7 +932,7 @@ class GraphQLDefinitionParser { loc: buildLocation(field.loc), metadata: null, name, - type: assertScalarFieldType(type), + type, }; } else { const selections = field.selectionSet @@ -954,7 +940,7 @@ class GraphQLDefinitionParser { : null; if (selections == null || selections.length === 0) { throw createUserError( - `Expected at least one selection for non-scalar field '${name}' on type '${String( + `Expected at least one selection for non-scalar field '${name}' on type '${schema.getTypeString( type, )}'.`, null, @@ -1080,7 +1066,13 @@ class GraphQLDefinitionParser { } const args = this._transformArguments( directive.arguments || [], - directiveDef.args, + directiveDef.args.map(item => { + return { + name: item.name, + type: item.type, + defaultValue: item.defaultValue, + }; + }), ); return { kind: 'Directive', @@ -1093,7 +1085,7 @@ class GraphQLDefinitionParser { _transformArguments( args: $ReadOnlyArray, - argumentDefinitions: $ReadOnlyArray, + argumentDefinitions: $ReadOnlyArray, ): $ReadOnlyArray { return args.map(arg => { const argName = getName(arg); @@ -1101,6 +1093,7 @@ class GraphQLDefinitionParser { if (argDef == null) { throw createUserError(`Unknown argument '${argName}'.`, null, [arg]); } + const value = this._transformValue(arg.value, argDef.type); return { kind: 'Argument', @@ -1163,10 +1156,7 @@ class GraphQLDefinitionParser { return [sortedConditions, otherDirectives]; } - _transformVariable( - ast: VariableNode, - usedAsType: ?GraphQLInputType, - ): Variable { + _transformVariable(ast: VariableNode, usedAsType: ?TypeID): Variable { const variableName = getName(ast); this._recordAndVerifyVariableReference(ast, variableName, usedAsType); return { @@ -1177,9 +1167,13 @@ class GraphQLDefinitionParser { }; } - _transformValue(ast: ValueNode, type: GraphQLInputType): ArgumentValue { - return transformValue(ast, type, (variableAst, variableType) => - this._transformVariable(variableAst, variableType), + _transformValue(ast: ValueNode, type: TypeID): ArgumentValue { + return transformValue( + this._schema, + ast, + type, + (variableAst, variableType) => + this._transformVariable(variableAst, variableType), ); } } @@ -1189,11 +1183,12 @@ class GraphQLDefinitionParser { * type. */ function transformValue( + schema: Schema, ast: ValueNode, - type: GraphQLInputType, + type: TypeID, transformVariable: ( variableAst: VariableNode, - variableType: GraphQLInputType, + variableType: TypeID, ) => ArgumentValue, options: {|+nonStrictEnums: boolean|} = {nonStrictEnums: false}, ): ArgumentValue { @@ -1202,7 +1197,7 @@ function transformValue( return transformVariable(ast, type); } else if (ast.kind === 'NullValue') { // Special case null literals since there is no value to parse - if (type instanceof GraphQLNonNull) { + if (schema.isNonNull(type)) { throw createUserError( `Expected a value matching type '${String(type)}'.`, null, @@ -1215,7 +1210,13 @@ function transformValue( value: null, }; } else { - return transformNonNullLiteral(ast, type, transformVariable, options); + return transformNonNullLiteral( + schema, + ast, + type, + transformVariable, + options, + ); } } @@ -1224,11 +1225,12 @@ function transformValue( * according to the expected type. */ function transformNonNullLiteral( + schema: Schema, ast: NonNullLiteralValueNode, - type: GraphQLInputType, + type: TypeID, transformVariable: ( variableAst: VariableNode, - variableType: GraphQLInputType, + variableType: TypeID, ) => ArgumentValue, options: {|+nonStrictEnums: boolean|}, ): ArgumentValue { @@ -1237,24 +1239,28 @@ function transformNonNullLiteral( // since that accurately describes to the user what the expected // type is (using nullableType would suggest that `null` is legal // even when it may not be, for example). - const nullableType = getNullableType(type); - if (nullableType instanceof GraphQLList) { + const nullableType = schema.getNullableType(type); + if (schema.isList(nullableType)) { if (ast.kind !== 'ListValue') { // Parse singular (non-list) values flowing into a list type // as scalars, ie without wrapping them in an array. return transformValue( + schema, ast, - nullableType.ofType, + schema.getNonListType(nullableType), transformVariable, options, ); } - const itemType = assertInputType(nullableType.ofType); + const itemType = schema.assertInputType( + schema.getNonListType(nullableType), + ); const literalList = []; const items = []; let areAllItemsScalar = true; ast.values.forEach(item => { const itemValue = transformValue( + schema, item, itemType, transformVariable, @@ -1279,11 +1285,10 @@ function transformNonNullLiteral( items, }; } - } else if (nullableType instanceof GraphQLInputObjectType) { - const objectType = nullableType; + } else if (schema.isInput(nullableType)) { if (ast.kind !== 'ObjectValue') { throw createUserError( - `Expected a value matching type '${String(type)}'.`, + `Expected a value matching type '${schema.getTypeString(type)}'.`, null, [ast], ); @@ -1293,16 +1298,20 @@ function transformNonNullLiteral( let areAllFieldsScalar = true; ast.fields.forEach(field => { const fieldName = getName(field); - const fieldConfig = objectType.getFields()[fieldName]; - if (fieldConfig == null) { + const fieldID = schema.getFieldByName(nullableType, fieldName); + if (!fieldID) { throw createUserError( - `Uknown field '${fieldName}' on type '${String(type)}'.`, + `Unknown field '${fieldName}' on type '${schema.getTypeString( + type, + )}.'`, null, [field], ); } - const fieldType = assertInputType(fieldConfig.type); + const fieldConfig = schema.getFieldConfig(fieldID); + const fieldType = schema.assertInputType(fieldConfig.type); const fieldValue = transformValue( + schema, field.value, fieldType, transformVariable, @@ -1332,7 +1341,7 @@ function transformNonNullLiteral( fields, }; } - } else if (nullableType === GraphQLID) { + } else if (schema.isId(nullableType)) { // GraphQLID's parseLiteral() always returns the string value. However // the int/string distinction may be important at runtime, so this // transform parses int/string literals into the corresponding JS types. @@ -1350,19 +1359,21 @@ function transformNonNullLiteral( }; } else { throw createUserError( - `Invalid value, expected a value matching type '${String(type)}'.`, + `Invalid value, expected a value matching type '${schema.getTypeString( + type, + )}'.`, null, [ast], ); } - } else if (nullableType instanceof GraphQLEnumType) { - const value = nullableType.parseLiteral(ast); + } else if (schema.isEnum(nullableType)) { + const value = schema.parseLiteral(nullableType, ast); if (value == null) { if (options.nonStrictEnums) { if (ast.kind === 'StringValue' || ast.kind === 'EnumValue') { const alternateValue = - nullableType.parseValue(ast.value.toUpperCase()) ?? - nullableType.parseValue(ast.value.toLowerCase()); + schema.parseValue(nullableType, ast.value.toUpperCase()) ?? + schema.parseValue(nullableType, ast.value.toLowerCase()); if (alternateValue != null) { // Use the original raw value return { @@ -1376,7 +1387,7 @@ function transformNonNullLiteral( // parseLiteral() should return a non-null JavaScript value // if the ast value is valid for the type. throw createUserError( - `Expected a value matching type '${String(type)}'.`, + `Expected a value matching type '${schema.getTypeString(type)}'.`, null, [ast], ); @@ -1386,13 +1397,13 @@ function transformNonNullLiteral( loc: buildLocation(ast.loc), value, }; - } else if (nullableType instanceof GraphQLScalarType) { - const value = nullableType.parseLiteral(ast); + } else if (schema.isScalar(nullableType)) { + const value = schema.parseLiteral(nullableType, ast); if (value == null) { // parseLiteral() should return a non-null JavaScript value // if the ast value is valid for the type. throw createUserError( - `Expected a value matching type '${String(type)}'.`, + `Expected a value matching type '${schema.getTypeString(type)}'.`, null, [ast], ); @@ -1403,9 +1414,8 @@ function transformNonNullLiteral( value, }; } else { - (nullableType: empty); throw createCompilerError( - `Unsupported type '${String( + `Unsupported type '${schema.getTypeString( type, )}' for input value, expected a GraphQLList, ` + 'GraphQLInputObjectType, GraphQLEnumType, or GraphQLScalarType.', @@ -1463,7 +1473,7 @@ function transformLiteralValue(ast: ValueNode, context: ASTNode): mixed { function buildArgumentDefinitions( variables: VariableDefinitions, ): $ReadOnlyArray { - return Array.from(variables.values(), ({ast, name, type, defaultValue}) => { + return Array.from(variables.values(), ({ast, name, defaultValue, type}) => { return { kind: 'LocalArgumentDefinition', loc: buildLocation(ast.loc), @@ -1489,29 +1499,6 @@ function buildLocation(loc: ?ASTLocation): Location { }; } -/** - * @private - */ -function isScalarFieldType(type: GraphQLOutputType): boolean { - const namedType = getNamedType(type); - return ( - namedType instanceof GraphQLScalarType || - namedType instanceof GraphQLEnumType - ); -} - -/** - * @private - */ -function assertScalarFieldType(type: GraphQLOutputType): ScalarFieldType { - if (!isScalarFieldType(type)) { - throw createUserError( - `Expected a scalar field type, got type '${String(type)}'.`, - ); - } - return (type: $FlowFixMe); -} - /** * @private */ diff --git a/packages/relay-compiler/core/RelayRecordIDFieldDefinition.js b/packages/relay-compiler/core/RelayRecordIDFieldDefinition.js deleted file mode 100644 index e973d6f886899..0000000000000 --- a/packages/relay-compiler/core/RelayRecordIDFieldDefinition.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict - * @format - */ - -'use strict'; - -const {GraphQLID, GraphQLNonNull} = require('graphql'); - -import type {GraphQLField} from 'graphql'; - -const RelayRecordIDFieldDefinition: GraphQLField = { - name: '__id', - type: GraphQLNonNull(GraphQLID), - description: 'The identity of a record in the Relay runtime store', - args: [], - resolve: undefined, - deprecationReason: undefined, - extensions: undefined, - astNode: undefined, -}; - -module.exports = RelayRecordIDFieldDefinition; diff --git a/packages/relay-compiler/core/RelayValidator.js b/packages/relay-compiler/core/RelayValidator.js index bcedd250a136d..3fea21dc6548d 100644 --- a/packages/relay-compiler/core/RelayValidator.js +++ b/packages/relay-compiler/core/RelayValidator.js @@ -29,23 +29,22 @@ const { ValuesOfCorrectTypeRule, VariablesAreInputTypesRule, formatError, - validate, } = require('graphql'); +import type {Schema} from './Schema'; import type { DocumentNode, FieldNode, - GraphQLSchema, ValidationRule, ValidationContext, } from 'graphql'; function validateOrThrow( + schema: Schema, document: DocumentNode, - schema: GraphQLSchema, rules: $ReadOnlyArray, ): void { - const validationErrors = validate(schema, document, rules); + const validationErrors = schema.DEPRECATED__validate(document, rules); if (validationErrors && validationErrors.length > 0) { const formattedErrors = validationErrors.map(formatError); const errorMessages = validationErrors.map(e => e.toString()); @@ -130,8 +129,8 @@ module.exports = { DisallowIdAsAliasValidationRule, ], validate: (Profiler.instrument(validateOrThrow, 'RelayValidator.validate'): ( + schema: Schema, document: DocumentNode, - schema: GraphQLSchema, rules: $ReadOnlyArray, ) => void), }; diff --git a/packages/relay-compiler/core/__tests__/GraphQLCompilerContext-test.js b/packages/relay-compiler/core/__tests__/GraphQLCompilerContext-test.js index bebe6fb85dca3..346cfee060b9a 100644 --- a/packages/relay-compiler/core/__tests__/GraphQLCompilerContext-test.js +++ b/packages/relay-compiler/core/__tests__/GraphQLCompilerContext-test.js @@ -11,26 +11,30 @@ 'use strict'; +const Schema = require('../Schema'); + describe('GraphQLCompilerContext', () => { let GraphQLCompilerContext; let RelayParser; - let TestSchema; let queryFoo; let fragmentBar; let fragmentFoo; + let compilerSchema; + let TestSchema; beforeEach(() => { jest.resetModules(); GraphQLCompilerContext = require('../GraphQLCompilerContext'); RelayParser = require('../RelayParser'); ({TestSchema} = require('relay-test-utils-internal')); + compilerSchema = Schema.DEPRECATED__create(TestSchema); }); describe('add()', () => { it('adds multiple roots', () => { [queryFoo, fragmentBar] = RelayParser.parse( - TestSchema, + compilerSchema, ` query Foo { node(id: 1) { ...Bar } } fragment Bar on Node { id } @@ -38,7 +42,7 @@ describe('GraphQLCompilerContext', () => { ); const context = [queryFoo, fragmentBar].reduce( (ctx, node) => ctx.add(node), - new GraphQLCompilerContext(TestSchema), + new GraphQLCompilerContext(compilerSchema), ); expect(context.getRoot('Foo')).toBe(queryFoo); @@ -47,14 +51,14 @@ describe('GraphQLCompilerContext', () => { it('throws if the document names are not unique', () => { [queryFoo, fragmentBar] = RelayParser.parse( - TestSchema, + compilerSchema, ` query Foo { node(id: 1) { ...Bar } } fragment Bar on Node { id } `, ); [fragmentFoo] = RelayParser.parse( - TestSchema, + compilerSchema, ` fragment Foo on Node { id } `, @@ -62,7 +66,7 @@ describe('GraphQLCompilerContext', () => { expect(() => { [queryFoo, fragmentBar, fragmentFoo].reduce( (ctx, node) => ctx.add(node), - new GraphQLCompilerContext(TestSchema), + new GraphQLCompilerContext(compilerSchema), ); }).toThrowError( 'GraphQLCompilerContext: Duplicate document named `Foo`. GraphQL ' + diff --git a/packages/relay-compiler/core/__tests__/GraphQLIRTransformer-test.js b/packages/relay-compiler/core/__tests__/GraphQLIRTransformer-test.js index 8347139e0efaa..6e10e4763fa8f 100644 --- a/packages/relay-compiler/core/__tests__/GraphQLIRTransformer-test.js +++ b/packages/relay-compiler/core/__tests__/GraphQLIRTransformer-test.js @@ -13,6 +13,7 @@ const GraphQLCompilerContext = require('../GraphQLCompilerContext'); const GraphQLIRTransformer = require('../GraphQLIRTransformer'); +const Schema = require('../Schema'); const {transformASTSchema} = require('../ASTConvert'); const {TestSchema, parseGraphQLText} = require('relay-test-utils-internal'); @@ -72,7 +73,9 @@ describe('GraphQLIRTransformer', () => { } `, ); - const context = new GraphQLCompilerContext(TestSchema).addAll(definitions); + const context = new GraphQLCompilerContext( + Schema.DEPRECATED__create(TestSchema), + ).addAll(definitions); const astKinds = [ 'Argument', diff --git a/packages/relay-compiler/core/__tests__/GraphQLIRValidator-test.js b/packages/relay-compiler/core/__tests__/GraphQLIRValidator-test.js index bcf7f45bf5793..17000d9df6530 100644 --- a/packages/relay-compiler/core/__tests__/GraphQLIRValidator-test.js +++ b/packages/relay-compiler/core/__tests__/GraphQLIRValidator-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../GraphQLCompilerContext'); const GraphQLIRTransformer = require('../GraphQLIRTransformer'); const GraphQLIRValidator = require('../GraphQLIRValidator'); +const Schema = require('../Schema'); const {transformASTSchema} = require('../ASTConvert'); const {TestSchema, parseGraphQLText} = require('relay-test-utils-internal'); @@ -73,7 +74,9 @@ describe('GraphQLIRValidator', () => { } `, ); - const context = new GraphQLCompilerContext(TestSchema).addAll(definitions); + const context = new GraphQLCompilerContext( + Schema.DEPRECATED__create(TestSchema), + ).addAll(definitions); const astKinds = [ 'Argument', diff --git a/packages/relay-compiler/core/__tests__/GraphQLIRVisitor-test.js b/packages/relay-compiler/core/__tests__/GraphQLIRVisitor-test.js index 54cbc81e9f9fc..c9f5cd3fb480d 100644 --- a/packages/relay-compiler/core/__tests__/GraphQLIRVisitor-test.js +++ b/packages/relay-compiler/core/__tests__/GraphQLIRVisitor-test.js @@ -13,6 +13,7 @@ const GraphQLIRPrinter = require('../GraphQLIRPrinter'); const RelayParser = require('../RelayParser'); +const Schema = require('../Schema'); const {visit} = require('../GraphQLIRVisitor'); const { @@ -43,20 +44,22 @@ type VisitNodeWithName = | Directive | ArgumentDefinition; +const schema = Schema.DEPRECATED__create(TestSchema); + describe('GraphQLIRVisitor', () => { generateTestsFromFixtures( `${__dirname}/fixtures/visitor/no-op-visit`, text => { - const ast = RelayParser.parse(TestSchema, text); + const ast = RelayParser.parse(schema, text); const sameAst = ast.map(fragment => visit(fragment, {})); - return sameAst.map(doc => GraphQLIRPrinter.print(doc)).join('\n'); + return sameAst.map(doc => GraphQLIRPrinter.print(schema, doc)).join('\n'); }, ); generateTestsFromFixtures( `${__dirname}/fixtures/visitor/mutate-visit`, text => { - const ast = RelayParser.parse(TestSchema, text); + const ast = RelayParser.parse(schema, text); const mutateNameVisitor = { leave: (node: VisitNodeWithName) => { return { @@ -129,7 +132,9 @@ describe('GraphQLIRVisitor', () => { }), ); - return mutatedAst.map(doc => GraphQLIRPrinter.print(doc)).join('\n'); + return mutatedAst + .map(doc => GraphQLIRPrinter.print(schema, doc)) + .join('\n'); }, ); }); diff --git a/packages/relay-compiler/core/__tests__/RelayCompilerScope-test.js b/packages/relay-compiler/core/__tests__/RelayCompilerScope-test.js index 867ab7bd0404e..9fae706a57fcf 100644 --- a/packages/relay-compiler/core/__tests__/RelayCompilerScope-test.js +++ b/packages/relay-compiler/core/__tests__/RelayCompilerScope-test.js @@ -10,30 +10,16 @@ 'use strict'; -describe('scope', () => { - let GraphQL; - let RelayCompilerScope; - let TestSchema; - - let GraphQLNonNull; - let getFragmentScope; - let getRootScope; - let optionalIntType; - let requiredIntType; - - beforeEach(() => { - jest.resetModules(); +const Schema = require('../Schema'); - GraphQL = require('graphql'); - RelayCompilerScope = require('../RelayCompilerScope'); - ({TestSchema} = require('relay-test-utils-internal')); +const {getRootScope, getFragmentScope} = require('../RelayCompilerScope'); +const {TestSchema} = require('relay-test-utils-internal'); - ({GraphQLNonNull} = GraphQL); - ({getFragmentScope, getRootScope} = RelayCompilerScope); +describe('scope', () => { + const schema = Schema.DEPRECATED__create(TestSchema); - optionalIntType = TestSchema.getType('Int'); - requiredIntType = new GraphQLNonNull(optionalIntType); - }); + const optionalIntType = schema.expectIntType(); + const requiredIntType = schema.getNonNullType(schema.expectIntType()); describe('getRootScope()', () => { it('creates variables for optional definitions with defaults', () => { @@ -118,6 +104,7 @@ describe('scope', () => { name: '', }; const innerScope = getFragmentScope( + schema, definitions, calls, outerScope, @@ -163,6 +150,7 @@ describe('scope', () => { name: '', }; const innerScope = getFragmentScope( + schema, definitions, calls, outerScope, @@ -207,6 +195,7 @@ describe('scope', () => { name: '', }; const innerScope = getFragmentScope( + schema, definitions, calls, outerScope, @@ -244,6 +233,7 @@ describe('scope', () => { name: '', }; const innerScope = getFragmentScope( + schema, definitions, calls, outerScope, @@ -280,8 +270,13 @@ describe('scope', () => { name: '', }; expect(() => { - // $FlowFixMe - loc is missing, but exists in LocalArgumentDefinition - getFragmentScope(definitions, calls, outerScope, fragmentSpread); + getFragmentScope( + schema, + definitions, + calls, + outerScope, + fragmentSpread, + ); }).toThrowErrorMatchingSnapshot(); }); @@ -310,6 +305,7 @@ describe('scope', () => { name: '', }; const innerScope = getFragmentScope( + schema, definitions, calls, outerScope, @@ -359,7 +355,13 @@ describe('scope', () => { name: '', }; expect(() => { - getFragmentScope(definitions, calls, outerScope, fragmentSpread); + getFragmentScope( + schema, + definitions, + calls, + outerScope, + fragmentSpread, + ); }).toThrowErrorMatchingSnapshot(); }); @@ -399,6 +401,7 @@ describe('scope', () => { name: '', }; const innerScope = getFragmentScope( + schema, definitions, calls, outerScope, diff --git a/packages/relay-compiler/core/__tests__/RelayParser-test.js b/packages/relay-compiler/core/__tests__/RelayParser-test.js index 3a9d40fdbe98d..1b5961fda3248 100644 --- a/packages/relay-compiler/core/__tests__/RelayParser-test.js +++ b/packages/relay-compiler/core/__tests__/RelayParser-test.js @@ -14,6 +14,7 @@ const ASTConvert = require('../ASTConvert'); const RelayMatchTransform = require('../../transforms/RelayMatchTransform'); const RelayParser = require('../RelayParser'); +const Schema = require('../Schema'); const { TestSchema, @@ -21,9 +22,12 @@ const { } = require('relay-test-utils-internal'); describe('RelayParser', () => { - const schema = ASTConvert.transformASTSchema(TestSchema, [ - RelayMatchTransform.SCHEMA_EXTENSION, - ]); + const schema = Schema.DEPRECATED__create( + TestSchema, + ASTConvert.transformASTSchema(TestSchema, [ + RelayMatchTransform.SCHEMA_EXTENSION, + ]), + ); /** * Regression tests for T24258497 @@ -42,7 +46,7 @@ describe('RelayParser', () => { id } }`; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); // Should also work when call that requires an ID! comes after a call that takes an ID text = `query TestQuery( @@ -55,7 +59,7 @@ describe('RelayParser', () => { id } }`; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); }); it('should parse fragment spread arguments with variable values', () => { @@ -67,7 +71,7 @@ describe('RelayParser', () => { viewer { actor { id } } } `; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); }); it('should parse fragment spread arguments with literal values', () => { @@ -79,7 +83,7 @@ describe('RelayParser', () => { viewer { actor { id } } } `; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); }); it('should error on fragment spread arguments with literal out of bounds values', () => { @@ -93,7 +97,7 @@ describe('RelayParser', () => { } `; expect(() => - RelayParser.parse(TestSchema, text), + RelayParser.parse(schema, text), ).toThrowErrorMatchingSnapshot(); }); @@ -110,7 +114,7 @@ describe('RelayParser', () => { id } }`; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); // Should also work when call that requires an ID! comes after a call that takes an ID text = `fragment TestFragment on Query @argumentDefinitions( @@ -123,7 +127,7 @@ describe('RelayParser', () => { id } }`; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); }); it('should not error when parsing a fragment that references undeclared variables without type errors', () => { @@ -135,7 +139,7 @@ describe('RelayParser', () => { title } }`; - expect(() => RelayParser.parse(TestSchema, text)).not.toThrowError(); + expect(() => RelayParser.parse(schema, text)).not.toThrowError(); }); it('should error when parsing fragment that references undeclared variables are used with differing types', () => { @@ -149,7 +153,7 @@ describe('RelayParser', () => { }`; let error; try { - RelayParser.parse(TestSchema, text); + RelayParser.parse(schema, text); } catch (error_) { error = error_; } diff --git a/packages/relay-compiler/core/__tests__/RelayPrinter-test.js b/packages/relay-compiler/core/__tests__/RelayPrinter-test.js index abe05a46bf716..956bc0b3313a3 100644 --- a/packages/relay-compiler/core/__tests__/RelayPrinter-test.js +++ b/packages/relay-compiler/core/__tests__/RelayPrinter-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../GraphQLCompilerContext'); const GraphQLIRPrinter = require('../GraphQLIRPrinter'); const RelayParser = require('../RelayParser'); +const Schema = require('../Schema'); const { TestSchema, @@ -22,11 +23,12 @@ const { describe('GraphQLIRPrinter', () => { generateTestsFromFixtures(`${__dirname}/fixtures/printer`, text => { - const ast = RelayParser.parse(TestSchema, text); - const context = new GraphQLCompilerContext(TestSchema).addAll(ast); + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + const ast = RelayParser.parse(compilerSchema, text); + const context = new GraphQLCompilerContext(compilerSchema).addAll(ast); const documents = []; context.forEachDocument(doc => { - documents.push(GraphQLIRPrinter.print(doc)); + documents.push(GraphQLIRPrinter.print(compilerSchema, doc)); }); return documents.join('\n'); }); diff --git a/packages/relay-compiler/core/__tests__/RelayValidator-test.js b/packages/relay-compiler/core/__tests__/RelayValidator-test.js index 6d43e7d81079e..b175e58b56335 100644 --- a/packages/relay-compiler/core/__tests__/RelayValidator-test.js +++ b/packages/relay-compiler/core/__tests__/RelayValidator-test.js @@ -13,13 +13,18 @@ const GraphQL = require('graphql'); const RelayValidator = require('../RelayValidator'); +const Schema = require('../../core/Schema'); const {TestSchema} = require('relay-test-utils-internal'); function validateString(input) { const ast = GraphQL.parse(new GraphQL.Source(input, 'test.graphql')); return () => { - RelayValidator.validate(ast, TestSchema, RelayValidator.LOCAL_RULES); + RelayValidator.validate( + Schema.DEPRECATED__create(TestSchema), + ast, + RelayValidator.LOCAL_RULES, + ); }; } diff --git a/packages/relay-compiler/core/__tests__/__snapshots__/RelayParser-test.js.snap b/packages/relay-compiler/core/__tests__/__snapshots__/RelayParser-test.js.snap index 85bf3c598c6f1..10cb9c1e81b2b 100644 --- a/packages/relay-compiler/core/__tests__/__snapshots__/RelayParser-test.js.snap +++ b/packages/relay-compiler/core/__tests__/__snapshots__/RelayParser-test.js.snap @@ -1956,11 +1956,13 @@ fragment TestFragment on User THROWN EXCEPTION: Error: RelayParser: Encountered 1 error(s): -- Unknown type: 'UnknownType'. +- Unknown type "UnknownType" referenced in the argument definitions. - GraphQL request (1:1) - 1: UnknownType - ^ + GraphQL request (3:24) + 2: fragment TestFragment on User + 3: @argumentDefinitions(myArg: {type: "UnknownType"}) { + ^ + 4: profilePicture(size: $PictureSize) { `; @@ -5290,7 +5292,7 @@ query LiteralObjectArgument3 { THROWN EXCEPTION: Error: RelayParser: Encountered 3 error(s): -- Uknown field 'unknownField' on type 'CheckinSearchInput'. +- Unknown field 'unknownField' on type 'CheckinSearchInput.' GraphQL request (5:7) 4: query: { @@ -5961,7 +5963,7 @@ fragment Foo on UnknownType { THROWN EXCEPTION: Error: RelayParser: Encountered 1 error(s): -- Unknown type: 'UnknownType'. +- Unknown type 'UnknownType' on the fragment definition 'Foo'. GraphQL request (2:17) 1: # expected-to-throw diff --git a/packages/relay-compiler/core/__tests__/filterContextForNode-test.js b/packages/relay-compiler/core/__tests__/filterContextForNode-test.js index eec11e6699ede..7e667e10e8764 100644 --- a/packages/relay-compiler/core/__tests__/filterContextForNode-test.js +++ b/packages/relay-compiler/core/__tests__/filterContextForNode-test.js @@ -13,6 +13,7 @@ const GraphQLCompilerContext = require('../GraphQLCompilerContext'); const GraphQLIRPrinter = require('../GraphQLIRPrinter'); +const Schema = require('../Schema'); const filterContextForNode = require('../filterContextForNode'); @@ -27,7 +28,11 @@ const MAIN_QUERY_NAME = 'MainQuery'; describe('filterContextForNode', () => { generateTestsFromFixtures(`${__dirname}/fixtures/filter-context`, text => { const {definitions} = parseGraphQLText(TestSchema, text); - const context = new GraphQLCompilerContext(TestSchema).addAll(definitions); + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + + const context = new GraphQLCompilerContext(compilerSchema).addAll( + definitions, + ); const printerContext = filterContextForNode( // $FlowFixMe - null or undefined is incompatible with union type context.get(MAIN_QUERY_NAME), @@ -35,7 +40,7 @@ describe('filterContextForNode', () => { ); return printerContext .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }); }); diff --git a/packages/relay-compiler/core/filterContextForNode.js b/packages/relay-compiler/core/filterContextForNode.js index 8bf85eca834ca..8099c3e732c70 100644 --- a/packages/relay-compiler/core/filterContextForNode.js +++ b/packages/relay-compiler/core/filterContextForNode.js @@ -25,10 +25,9 @@ function filterContextForNode( context: GraphQLCompilerContext, ): GraphQLCompilerContext { const queue = [node]; - let filteredContext = new GraphQLCompilerContext( - context.serverSchema, - context.clientSchema, - ).add(node); + let filteredContext = new GraphQLCompilerContext(context.getSchema()).add( + node, + ); const visitFragmentSpread = (fragmentSpread: FragmentSpread) => { const {name} = fragmentSpread; if (!filteredContext.get(name)) { diff --git a/packages/relay-compiler/core/getFieldDefinition.js b/packages/relay-compiler/core/getFieldDefinition.js index cf33d812c68d5..dcbe1fbf0ebf1 100644 --- a/packages/relay-compiler/core/getFieldDefinition.js +++ b/packages/relay-compiler/core/getFieldDefinition.js @@ -10,66 +10,51 @@ 'use strict'; -const RelayRecordIDFieldDefinition = require('./RelayRecordIDFieldDefinition'); - -const {getRawType} = require('./GraphQLSchemaUtils'); const {createCompilerError} = require('./RelayCompilerError'); -const { - assertAbstractType, - GraphQLInterfaceType, - GraphQLObjectType, - GraphQLUnionType, - isAbstractType, - SchemaMetaFieldDef, - TypeMetaFieldDef, - TypeNameMetaFieldDef, -} = require('graphql'); +const {SchemaMetaFieldDef, TypeMetaFieldDef} = require('graphql'); -import type { - GraphQLSchema, - GraphQLOutputType, - FieldNode, - GraphQLField, - GraphQLType, -} from 'graphql'; +import type {Schema, TypeID, FieldID} from './Schema'; +import type {FieldNode} from 'graphql'; export type GetFieldDefinitionFn = ( - schema: GraphQLSchema, - parentType: GraphQLOutputType, + schema: Schema, + parentType: TypeID, fieldName: string, fieldAST: FieldNode, -) => ?GraphQLField; +) => ?FieldID; /** * Find the definition of a field of the specified type using strict * resolution rules per the GraphQL spec. */ function getFieldDefinitionStrict( - schema: GraphQLSchema, - parentType: GraphQLOutputType, + schema: Schema, + parentType: TypeID, fieldName: string, -): ?GraphQLField { - const type = getRawType(parentType); - const isQueryType = type === schema.getQueryType(); - const hasTypeName = - type instanceof GraphQLObjectType || - type instanceof GraphQLInterfaceType || - type instanceof GraphQLUnionType; +): ?FieldID { + const type = schema.getRawType(parentType); + const queryType = schema.getQueryType(); + const isQueryType = + queryType != null && schema.areEqualTypes(type, queryType); + const hasTypeName = schema.isAbstractType(type) || schema.isObject(type); let schemaFieldDef; if (isQueryType && fieldName === SchemaMetaFieldDef.name) { - schemaFieldDef = SchemaMetaFieldDef; + schemaFieldDef = + queryType != null ? schema.getFieldByName(queryType, '__schema') : null; } else if (isQueryType && fieldName === TypeMetaFieldDef.name) { - schemaFieldDef = TypeMetaFieldDef; - } else if (hasTypeName && fieldName === TypeNameMetaFieldDef.name) { - schemaFieldDef = TypeNameMetaFieldDef; - } else if (hasTypeName && fieldName === RelayRecordIDFieldDefinition.name) { - schemaFieldDef = RelayRecordIDFieldDefinition; - } else if ( - type instanceof GraphQLInterfaceType || - type instanceof GraphQLObjectType - ) { - schemaFieldDef = type.getFields()[fieldName]; + schemaFieldDef = + queryType != null ? schema.getFieldByName(queryType, '__type') : null; + } else if (hasTypeName && fieldName === '__typename') { + schemaFieldDef = schema.getFieldByName(type, '__typename'); + } else if (hasTypeName && fieldName === '__id') { + schemaFieldDef = schema.getFieldByName(type, '__id'); + } else if (schema.isInterface(type) || schema.isObject(type)) { + if (schema.hasField(type, fieldName)) { + schemaFieldDef = schema.getFieldByName(type, fieldName); + } else { + return null; + } } return schemaFieldDef; } @@ -80,55 +65,56 @@ function getFieldDefinitionStrict( * to legacy mode that supports fat interfaces. */ function getFieldDefinitionLegacy( - schema: GraphQLSchema, - parentType: GraphQLOutputType, + schema: Schema, + parentType: TypeID, fieldName: string, fieldAST: FieldNode, -): ?GraphQLField { +): ?FieldID { let schemaFieldDef = getFieldDefinitionStrict(schema, parentType, fieldName); + if (!schemaFieldDef) { - const type = getRawType(parentType); schemaFieldDef = getFieldDefinitionLegacyImpl( schema, - type, + parentType, fieldName, fieldAST, ); } - return schemaFieldDef || null; + return schemaFieldDef ?? null; } /** * @private */ function getFieldDefinitionLegacyImpl( - schema: GraphQLSchema, - type: GraphQLType, + schema: Schema, + type: TypeID, fieldName: string, fieldAST: FieldNode, -): ?GraphQLField { +): ?FieldID { + const rawType = schema.getRawType(type); if ( - isAbstractType(type) && + schema.isAbstractType(rawType) && fieldAST && fieldAST.directives && fieldAST.directives.some( directive => getName(directive) === 'fixme_fat_interface', ) ) { - const possibleTypes = schema.getPossibleTypes(assertAbstractType(type)); + const possibleTypes = schema.getPossibleTypes(rawType); let schemaFieldDef; for (let ii = 0; ii < possibleTypes.length; ii++) { - const possibleField = possibleTypes[ii].getFields()[fieldName]; + const possibleField = schema.getFieldByName(possibleTypes[ii], fieldName); if (possibleField) { // Fat interface fields can have differing arguments. Try to return // a field with matching arguments, but still return a field if the // arguments do not match. schemaFieldDef = possibleField; if (fieldAST && fieldAST.arguments) { - const argumentsAllExist = fieldAST.arguments.every(argument => - possibleField.args.find( - argDef => argDef.name === getName(argument), - ), + const argumentsAllExist = fieldAST.arguments.every( + argument => + schema.getFieldArgByName(possibleField, getName(argument)) != + null, ); if (argumentsAllExist) { break; diff --git a/packages/relay-compiler/core/getIdentifierForArgumentValue.js b/packages/relay-compiler/core/getIdentifierForArgumentValue.js index 3925085d1fc5e..e3c7fb1abe119 100644 --- a/packages/relay-compiler/core/getIdentifierForArgumentValue.js +++ b/packages/relay-compiler/core/getIdentifierForArgumentValue.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/relay-compiler/core/getIdentifierForSelection.js b/packages/relay-compiler/core/getIdentifierForSelection.js index 03a7a2b0ac6ad..f1a07ba895f81 100644 --- a/packages/relay-compiler/core/getIdentifierForSelection.js +++ b/packages/relay-compiler/core/getIdentifierForSelection.js @@ -15,13 +15,14 @@ const invariant = require('invariant'); const {printArguments, printDirectives} = require('./GraphQLIRPrinter'); import type {Selection} from './GraphQLIR'; +import type {Schema} from './Schema'; /** * Generates an identifier that is unique to a given selection: the alias for * fields, the type for inline fragments, and a summary of the condition * variable and passing value for conditions. */ -function getIdentifierForSelection(node: Selection): string { +function getIdentifierForSelection(schema: Schema, node: Selection): string { if ( node.kind === 'LinkedField' || node.kind === 'ScalarField' || @@ -29,13 +30,13 @@ function getIdentifierForSelection(node: Selection): string { ) { return 'Field: ' + node.directives.length === 0 ? node.alias - : node.alias + printDirectives(node.directives); + : node.alias + printDirectives(schema, node.directives); } else if (node.kind === 'Connection') { return 'Connection:' + node.label; } else if (node.kind === 'FragmentSpread') { return 'FragmentSpread:' + node.args.length === 0 ? node.name - : node.name + printArguments(node.args); + : node.name + printArguments(schema, node.args); } else if (node.kind === 'ModuleImport') { return 'ModuleImport:'; } else if (node.kind === 'Defer') { @@ -43,7 +44,7 @@ function getIdentifierForSelection(node: Selection): string { } else if (node.kind === 'Stream') { return 'Stream:' + node.label; } else if (node.kind === 'InlineFragment') { - return 'InlineFragment:' + node.typeCondition.name; + return 'InlineFragment:' + schema.getTypeString(node.typeCondition); } else if (node.kind === 'ClientExtension') { return 'ClientExtension:'; } else if (node.kind === 'InlineDataFragmentSpread') { diff --git a/packages/relay-compiler/core/getLiteralArgumentValues.js b/packages/relay-compiler/core/getLiteralArgumentValues.js index 5235c380bb09e..6e0dfca2bbd0d 100644 --- a/packages/relay-compiler/core/getLiteralArgumentValues.js +++ b/packages/relay-compiler/core/getLiteralArgumentValues.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format */ diff --git a/packages/relay-compiler/core/inferRootArgumentDefinitions.js b/packages/relay-compiler/core/inferRootArgumentDefinitions.js index cd587d63a48ac..fe6a20341c52b 100644 --- a/packages/relay-compiler/core/inferRootArgumentDefinitions.js +++ b/packages/relay-compiler/core/inferRootArgumentDefinitions.js @@ -14,7 +14,6 @@ const GraphQLCompilerContext = require('./GraphQLCompilerContext'); const GraphQLIRVisitor = require('./GraphQLIRVisitor'); const {createCompilerError} = require('./RelayCompilerError'); -const {GraphQLNonNull, GraphQLBoolean, GraphQLString} = require('graphql'); import type { Argument, @@ -60,10 +59,7 @@ function inferRootArgumentDefinitions( // Because @argument values don't matter (only variable names/types), // each reachable fragment only has to be checked once. const transformed = new Map(); - const nextContext = new GraphQLCompilerContext( - context.serverSchema, - context.clientSchema, - ); + const nextContext = new GraphQLCompilerContext(context.getSchema()); return nextContext.addAll( Array.from(context.documents(), node => { switch (node.kind) { @@ -122,12 +118,13 @@ function transformRoot( ); } const localDefinition = localArgumentDefinitions.get(argDef.name); + const type = localDefinition?.type ?? argDef.type; return { defaultValue: localDefinition?.defaultValue ?? null, kind: 'LocalArgumentDefinition', loc: argDef.loc, name: argDef.name, - type: localDefinition?.type ?? argDef.type, + type: type, }; }), }; @@ -223,7 +220,7 @@ function visit( kind: 'RootArgumentDefinition', loc: {kind: 'Derived', source: argument.loc}, name: variable.variableName, - type, + type: type, }); } return false; @@ -233,14 +230,18 @@ function visit( if (variable.kind !== 'Variable') { return; } - const type = variable.type ?? new GraphQLNonNull(GraphQLBoolean); + const type = + variable.type ?? + context + .getSchema() + .getNonNullType(context.getSchema().expectBooleanType()); if (!argumentDefinitions.has(variable.variableName)) { // root variable argumentDefinitions.set(variable.variableName, { kind: 'RootArgumentDefinition', loc: {kind: 'Derived', source: variable.loc}, name: variable.variableName, - type, + type: type, }); } }, @@ -253,7 +254,11 @@ function visit( if (variable == null || variable.kind !== 'Variable') { return; } - const type = variable.type ?? new GraphQLNonNull(GraphQLBoolean); + const type = + variable.type ?? + context + .getSchema() + .getNonNullType(context.getSchema().expectBooleanType()); if (!argumentDefinitions.has(variable.variableName)) { // root variable argumentDefinitions.set(variable.variableName, { @@ -270,14 +275,18 @@ function visit( if (variable == null || variable.kind !== 'Variable') { return; } - const type = variable.type ?? new GraphQLNonNull(GraphQLBoolean); + const type = + variable.type ?? + context + .getSchema() + .getNonNullType(context.getSchema().expectBooleanType()); if (!argumentDefinitions.has(variable.variableName)) { // root variable argumentDefinitions.set(variable.variableName, { kind: 'RootArgumentDefinition', loc: {kind: 'Derived', source: variable.loc}, name: variable.variableName, - type, + type: type, }); } }, @@ -286,7 +295,11 @@ function visit( if (variable == null || variable.kind !== 'Variable') { return; } - const type = variable.type ?? new GraphQLNonNull(GraphQLBoolean); + const type = + variable.type ?? + context + .getSchema() + .getNonNullType(context.getSchema().expectBooleanType()); if (!argumentDefinitions.has(variable.variableName)) { // root variable argumentDefinitions.set(variable.variableName, { @@ -307,14 +320,14 @@ function visit( if (variable == null) { return; } - const type = variable.type ?? GraphQLString; + const type = variable.type ?? context.getSchema().expectStringType(); if (!argumentDefinitions.has(variable.variableName)) { // root variable argumentDefinitions.set(variable.variableName, { kind: 'RootArgumentDefinition', loc: {kind: 'Derived', source: variable.loc}, name: variable.variableName, - type, + type: type, }); } }); diff --git a/packages/relay-compiler/core/isEquivalentType.js b/packages/relay-compiler/core/isEquivalentType.js deleted file mode 100644 index 10428bdc5af9a..0000000000000 --- a/packages/relay-compiler/core/isEquivalentType.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -'use strict'; - -const {getRawType} = require('../core/GraphQLSchemaUtils'); -const {GraphQLNonNull, GraphQLList} = require('graphql'); - -import type {GraphQLType} from 'graphql'; - -/** - * Determine if a type is the same type (same name and class) as another type. - * Needed if we're comparing IRs created at different times: we don't yet have - * an IR schema, so the type we assign to an IR field could be !== than - * what we assign to it after adding some schema definitions or extensions. - */ -function isEquivalentType(typeA: GraphQLType, typeB: GraphQLType): boolean { - // Easy short-circuit: equal types are equal. - if (typeA === typeB) { - return true; - } - - // If either type is non-null, the other must also be non-null. - if (typeA instanceof GraphQLNonNull && typeB instanceof GraphQLNonNull) { - return isEquivalentType(typeA.ofType, typeB.ofType); - } - - // If either type is a list, the other must also be a list. - if (typeA instanceof GraphQLList && typeB instanceof GraphQLList) { - return isEquivalentType(typeA.ofType, typeB.ofType); - } - - // Make sure the two types are of the same class - if (typeA.constructor.name === typeB.constructor.name) { - const rawA = getRawType(typeA); - const rawB = getRawType(typeB); - - // And they must have the exact same name - return rawA.name === rawB.name; - } - - // Otherwise the types are not equal. - return false; -} - -module.exports = isEquivalentType; diff --git a/packages/relay-compiler/handlers/connection/RelayConnectionTransform.js b/packages/relay-compiler/handlers/connection/RelayConnectionTransform.js index e5ba6da83e7f6..75aacccb4a94b 100644 --- a/packages/relay-compiler/handlers/connection/RelayConnectionTransform.js +++ b/packages/relay-compiler/handlers/connection/RelayConnectionTransform.js @@ -12,7 +12,6 @@ const IRTransformer = require('../../core/GraphQLIRTransformer'); const RelayParser = require('../../core/RelayParser'); -const SchemaUtils = require('../../core/GraphQLSchemaUtils'); const getLiteralArgumentValues = require('../../core/getLiteralArgumentValues'); @@ -21,15 +20,7 @@ const { createUserError, } = require('../../core/RelayCompilerError'); const {AFTER, BEFORE, FIRST, KEY, LAST} = require('./RelayConnectionConstants'); -const { - GraphQLInterfaceType, - GraphQLList, - GraphQLObjectType, - GraphQLScalarType, - GraphQLString, - GraphQLUnionType, - parse, -} = require('graphql'); +const {parse} = require('graphql'); const {ConnectionInterface, RelayFeatureFlags} = require('relay-runtime'); import type CompilerContext from '../../core/GraphQLCompilerContext'; @@ -46,6 +37,7 @@ import type { Variable, Location, } from '../../core/GraphQLIR'; +import type {Schema, TypeID} from '../../core/Schema'; import type {ConnectionMetadata} from 'relay-runtime'; type Options = { @@ -140,8 +132,12 @@ function visitFragmentOrRoot( * @internal */ function visitLinkedField(field: LinkedField, options: Options): LinkedField { - const nullableType = SchemaUtils.getNullableType(field.type); - const isPlural = nullableType instanceof GraphQLList; + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); + + const nullableType = schema.getNullableType(field.type); + + const isPlural = schema.isList(nullableType); const path = options.path.concat(isPlural ? null : field.alias || field.name); let transformedField: LinkedField = this.traverse(field, { ...options, @@ -154,20 +150,22 @@ function visitLinkedField(field: LinkedField, options: Options): LinkedField { if (!connectionDirective) { return transformedField; } - if ( - !(nullableType instanceof GraphQLObjectType) && - !(nullableType instanceof GraphQLInterfaceType) - ) { + if (!schema.isObject(nullableType) && !schema.isInterface(nullableType)) { throw new createUserError( `@${connectionDirective.name} used on invalid field '${field.name}'. ` + 'Expected the return type to be a non-plural interface or object, ' + - `got '${String(field.type)}'.`, + `got '${schema.getTypeString(field.type)}'.`, [transformedField.loc], ); } validateConnectionSelection(transformedField); - validateConnectionType(transformedField, nullableType, connectionDirective); + validateConnectionType( + schema, + transformedField, + nullableType, + connectionDirective, + ); const connectionArguments = buildConnectionArguments( transformedField, @@ -390,11 +388,12 @@ function buildConnectionMetadata( function transformConnectionSelections( context: CompilerContext, field: LinkedField, - nullableType: GraphQLInterfaceType | GraphQLObjectType, + nullableType: TypeID, direction: 'forward' | 'backward' | 'bidirectional', connectionArguments: ConnectionArguments, directiveLocation: Location, ): $ReadOnlyArray { + const schema = context.getSchema(); const derivedFieldLocation = {kind: 'Derived', source: field.loc}; const derivedDirectiveLocation = { kind: 'Derived', @@ -451,7 +450,7 @@ function transformConnectionSelections( kind: 'Argument', loc: derivedDirectiveLocation, name: 'label', - type: GraphQLString, + type: context.getSchema().expectStringType(), value: { kind: 'Literal', loc: derivedDirectiveLocation, @@ -470,7 +469,7 @@ function transformConnectionSelections( kind: 'Argument', loc: derivedDirectiveLocation, name: 'label', - type: GraphQLString, + type: context.getSchema().expectStringType(), value: { kind: 'Literal', loc: derivedDirectiveLocation, @@ -517,8 +516,14 @@ function transformConnectionSelections( | LinkedField | InlineFragment ) = pageInfoSelection; - const edgesType = nullableType.getFields()[EDGES].type; - const pageInfoType = nullableType.getFields()[PAGE_INFO].type; + const edgesType = schema.getFieldConfig( + schema.expectField(nullableType, EDGES), + ).type; + + const pageInfoType = schema.getFieldConfig( + schema.expectField(nullableType, PAGE_INFO), + ).type; + if (transformedEdgesSelection == null) { transformedEdgesSelection = { alias: EDGES, @@ -552,20 +557,26 @@ function transformConnectionSelections( // Generate (additional) fields on pageInfo and add to the transformed // pageInfo field - const pageInfoRawType = SchemaUtils.getRawType(pageInfoType); + const pageInfoRawType = schema.getRawType(pageInfoType); let pageInfoText; if (direction === 'forward') { - pageInfoText = `fragment PageInfo on ${String(pageInfoRawType)} { + pageInfoText = `fragment PageInfo on ${schema.getTypeString( + pageInfoRawType, + )} { ${END_CURSOR} ${HAS_NEXT_PAGE} }`; } else if (direction === 'backward') { - pageInfoText = `fragment PageInfo on ${String(pageInfoRawType)} { + pageInfoText = `fragment PageInfo on ${schema.getTypeString( + pageInfoRawType, + )} { ${HAS_PREV_PAGE} ${START_CURSOR} }`; } else { - pageInfoText = `fragment PageInfo on ${String(pageInfoRawType)} { + pageInfoText = `fragment PageInfo on ${schema.getTypeString( + pageInfoRawType, + )} { ${END_CURSOR} ${HAS_NEXT_PAGE} ${HAS_PREV_PAGE} @@ -573,7 +584,7 @@ function transformConnectionSelections( }`; } const pageInfoAst = parse(pageInfoText); - const pageInfoFragment = RelayParser.transform(context.clientSchema, [ + const pageInfoFragment = RelayParser.transform(context.getSchema(), [ pageInfoAst.definitions[0], ])[0]; if (transformedPageInfoSelection.kind !== 'LinkedField') { @@ -592,8 +603,8 @@ function transformConnectionSelections( kind: 'InlineFragment', loc: derivedFieldLocation, metadata: null, - typeCondition: pageInfoFragment.type, selections: pageInfoFragment.selections, + typeCondition: pageInfoFragment.type, }, ], }; @@ -604,15 +615,15 @@ function transformConnectionSelections( kind: 'InlineFragment', loc: derivedFieldLocation, metadata: null, - typeCondition: nullableType, selections: [transformedPageInfoSelection], + typeCondition: nullableType, }; } // Generate additional fields on edges and append to the transformed edges // selection const edgeText = ` - fragment Edges on ${String(SchemaUtils.getRawType(edgesType))} { + fragment Edges on ${schema.getTypeString(schema.getRawType(edgesType))} { ${CURSOR} ${NODE} { __typename # rely on GenerateRequisiteFieldTransform to add "id" @@ -620,7 +631,7 @@ function transformConnectionSelections( } `; const edgeAst = parse(edgeText); - const edgeFragment = RelayParser.transform(context.clientSchema, [ + const edgeFragment = RelayParser.transform(context.getSchema(), [ edgeAst.definitions[0], ])[0]; // When streaming the edges field needs @stream @@ -637,8 +648,8 @@ function transformConnectionSelections( kind: 'InlineFragment', loc: derivedFieldLocation, metadata: null, - typeCondition: edgeFragment.type, selections: edgeFragment.selections, + typeCondition: edgeFragment.type, }, ], }; @@ -724,8 +735,9 @@ function validateConnectionSelection(field: LinkedField): void { * subfields. */ function validateConnectionType( + schema: Schema, field: LinkedField, - nullableType: GraphQLInterfaceType | GraphQLObjectType, + nullableType: TypeID, connectionDirective: Directive, ): void { const directiveName = connectionDirective.name; @@ -740,11 +752,8 @@ function validateConnectionType( START_CURSOR, } = ConnectionInterface.get(); - const typeName = String(nullableType); - const typeFields = nullableType.getFields(); - const edges = typeFields[EDGES]; - - if (edges == null) { + const typeName = schema.getTypeString(nullableType); + if (!schema.hasField(nullableType, EDGES)) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have an '${EDGES}' field`, @@ -752,8 +761,12 @@ function validateConnectionType( ); } - const edgesType = SchemaUtils.getNullableType(edges.type); - if (!(edgesType instanceof GraphQLList)) { + const edges = schema.getFieldConfig( + schema.expectField(nullableType, EDGES, null, [field.loc]), + ); + + const edgesType = schema.getNullableType(edges.type); + if (!schema.isList(edgesType)) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have an '${EDGES}' field that returns ` + @@ -761,11 +774,8 @@ function validateConnectionType( [field.loc], ); } - const edgeType = SchemaUtils.getNullableType(edgesType.ofType); - if ( - !(edgeType instanceof GraphQLObjectType) && - !(edgeType instanceof GraphQLInterfaceType) - ) { + const edgeType = schema.getNullableType(schema.getNonListType(edgesType)); + if (!schema.isObject(edgeType) && !schema.isInterface(edgeType)) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have an '${EDGES}' field that returns ` + @@ -774,8 +784,7 @@ function validateConnectionType( ); } - const node = edgeType.getFields()[NODE]; - if (node == null) { + if (!schema.hasField(edgeType, NODE)) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have an '${EDGES} { ${NODE} }' field ` + @@ -783,14 +792,12 @@ function validateConnectionType( [field.loc], ); } - const nodeType = SchemaUtils.getNullableType(node.type); - if ( - !( - nodeType instanceof GraphQLInterfaceType || - nodeType instanceof GraphQLUnionType || - nodeType instanceof GraphQLObjectType - ) - ) { + const node = schema.getFieldConfig( + schema.expectField(edgeType, NODE, null, [field.loc]), + ); + + const nodeType = schema.getNullableType(node.type); + if (!(schema.isAbstractType(nodeType) || schema.isObject(nodeType))) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have an '${EDGES} { ${NODE} }' field ` + @@ -799,11 +806,19 @@ function validateConnectionType( ); } - const cursor = edgeType.getFields()[CURSOR]; - if ( - cursor == null || - !(SchemaUtils.getNullableType(cursor.type) instanceof GraphQLScalarType) - ) { + if (!schema.hasField(edgeType, CURSOR)) { + throw createUserError( + `@${directiveName} used on invalid field '${field.name}'. Expected the ` + + `field type '${typeName}' to have an '${EDGES} { ${CURSOR} }' field ` + + 'that returns a scalar value.', + [field.loc], + ); + } + const cursor = schema.getFieldConfig( + schema.expectField(edgeType, CURSOR, null, [field.loc]), + ); + + if (!schema.isScalar(schema.getNullableType(cursor.type))) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have an '${EDGES} { ${CURSOR} }' field ` + @@ -812,8 +827,7 @@ function validateConnectionType( ); } - const pageInfo = typeFields[PAGE_INFO]; - if (pageInfo == null) { + if (!schema.hasField(nullableType, PAGE_INFO)) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have a '${PAGE_INFO}' field that returns ` + @@ -821,8 +835,13 @@ function validateConnectionType( [field.loc], ); } - const pageInfoType = SchemaUtils.getNullableType(pageInfo.type); - if (!(pageInfoType instanceof GraphQLObjectType)) { + + const pageInfo = schema.getFieldConfig( + schema.expectField(nullableType, PAGE_INFO, null, [field.loc]), + ); + + const pageInfoType = schema.getNullableType(pageInfo.type); + if (!schema.isObject(pageInfoType)) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected the ` + `field type '${typeName}' to have a '${PAGE_INFO}' field that ` + @@ -833,14 +852,10 @@ function validateConnectionType( [END_CURSOR, HAS_NEXT_PAGE, HAS_PREV_PAGE, START_CURSOR].forEach( fieldName => { - const pageInfoField = pageInfoType.getFields()[fieldName]; - if ( - pageInfoField == null || - !( - SchemaUtils.getNullableType(pageInfoField.type) instanceof - GraphQLScalarType - ) - ) { + const pageInfoField = schema.getFieldConfig( + schema.expectField(pageInfoType, fieldName, null, [field.loc]), + ); + if (!schema.isScalar(schema.getNullableType(pageInfoField.type))) { throw createUserError( `@${directiveName} used on invalid field '${field.name}'. Expected ` + `the field type '${typeName}' to have a '${PAGE_INFO} { ${fieldName} }' ` + diff --git a/packages/relay-compiler/handlers/connection/__tests__/RelayConnectionTransform-test.js b/packages/relay-compiler/handlers/connection/__tests__/RelayConnectionTransform-test.js index 60e407bd05b3f..51f9773543df6 100644 --- a/packages/relay-compiler/handlers/connection/__tests__/RelayConnectionTransform-test.js +++ b/packages/relay-compiler/handlers/connection/__tests__/RelayConnectionTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../../core/GraphQLIRPrinter'); const RelayConnectionTransform = require('../RelayConnectionTransform'); +const Schema = require('../../../core/Schema'); const {transformASTSchema} = require('../../../core/ASTConvert'); const { @@ -24,17 +25,21 @@ const { describe('RelayConnectionTransform', () => { generateTestsFromFixtures(`${__dirname}/fixtures`, text => { - const schema = transformASTSchema(TestSchema, [ + const extendedSchema = transformASTSchema(TestSchema, [ RelayConnectionTransform.SCHEMA_EXTENSION, ]); - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([RelayConnectionTransform.transform]) .documents() .map( doc => - GraphQLIRPrinter.print(doc) + + GraphQLIRPrinter.print(compilerSchema, doc) + '# Metadata:\n' + JSON.stringify(doc.metadata ?? null, null, 2), ) diff --git a/packages/relay-compiler/index.js b/packages/relay-compiler/index.js index c8dc558dee745..014268087a235 100644 --- a/packages/relay-compiler/index.js +++ b/packages/relay-compiler/index.js @@ -25,7 +25,6 @@ const GraphQLIRPrinter = require('./core/GraphQLIRPrinter'); const GraphQLIRTransformer = require('./core/GraphQLIRTransformer'); const GraphQLIRVisitor = require('./core/GraphQLIRVisitor'); const GraphQLMultiReporter = require('./reporters/GraphQLMultiReporter'); -const GraphQLSchemaUtils = require('./core/GraphQLSchemaUtils'); const GraphQLWatchmanClient = require('./core/GraphQLWatchmanClient'); const RelayCodeGenerator = require('./codegen/RelayCodeGenerator'); const RelayFileWriter = require('./codegen/RelayFileWriter'); @@ -33,9 +32,11 @@ const RelayFlowGenerator = require('./language/javascript/RelayFlowGenerator'); const RelayIRTransforms = require('./core/RelayIRTransforms'); const RelayIRValidations = require('./core/RelayIRValidations'); const RelayParser = require('./core/RelayParser'); +const RelaySchema = require('./core/Schema'); const RelaySourceModuleParser = require('./core/RelaySourceModuleParser'); const RelayValidator = require('./core/RelayValidator'); const Rollout = require('./util/Rollout'); +const SchemaUtils = require('./core/SchemaUtils'); const compileRelayArtifacts = require('./codegen/compileRelayArtifacts'); const filterContextForNode = require('./core/filterContextForNode'); @@ -43,7 +44,6 @@ const formatGeneratedModule = require('./language/javascript/formatGeneratedModu const getIdentifierForArgumentValue = require('./core/getIdentifierForArgumentValue'); const getLiteralArgumentValues = require('./core/getLiteralArgumentValues'); const getNormalizationOperationName = require('./core/getNormalizationOperationName'); -const isEquivalentType = require('./core/isEquivalentType'); const nullthrows = require('./util/nullthrowsOSS'); const writeRelayGeneratedFile = require('./codegen/writeRelayGeneratedFile'); @@ -96,11 +96,11 @@ export type { Root, RootArgumentDefinition, ScalarField, - ScalarFieldType, Selection, SplitOperation, Variable, } from './core/GraphQLIR'; +export type {Schema, TypeID, FieldID} from './core/Schema'; export type { FormatModule, TypeGenerator, @@ -129,17 +129,18 @@ module.exports = { Printer: GraphQLIRPrinter, Profiler: GraphQLCompilerProfiler, Rollout, - SchemaUtils: GraphQLSchemaUtils, + SchemaUtils: SchemaUtils, SourceControlMercurial, WatchmanClient: GraphQLWatchmanClient, + filterContextForNode, getIdentifierForArgumentValue, getNormalizationOperationName, getLiteralArgumentValues, - isEquivalentType, nullthrows, Parser: RelayParser, + Schema: RelaySchema, Validator: RelayValidator, CodeGenerator: RelayCodeGenerator, FlowGenerator: RelayFlowGenerator, diff --git a/packages/relay-compiler/language/RelayLanguagePluginInterface.js b/packages/relay-compiler/language/RelayLanguagePluginInterface.js index 3076ffb19c70a..d3ab206104bbf 100644 --- a/packages/relay-compiler/language/RelayLanguagePluginInterface.js +++ b/packages/relay-compiler/language/RelayLanguagePluginInterface.js @@ -14,6 +14,7 @@ const {RelayConcreteNode} = require('relay-runtime'); import type {IRTransform} from '../core/GraphQLCompilerContext'; import type {GeneratedDefinition, Root, Fragment} from '../core/GraphQLIR'; +import type {Schema} from '../core/Schema'; import type {ScalarTypeMapping} from './javascript/RelayFlowTypeTransformers'; import type {GeneratedNode} from 'relay-runtime'; @@ -175,6 +176,11 @@ export type FormatModule = ({| * The generated node being written. */ node: GeneratedNode, + + /** + * GraphQL Schema Interface + */ + schema: Schema, |}) => string; /** @@ -263,5 +269,9 @@ export type TypeGenerator = { * for e.g. the selections made. It can, however, also generate any other * content such as importing other files, including other artifacts. */ - generate: (node: Root | Fragment, options: TypeGeneratorOptions) => string, + generate: ( + schema: Schema, + node: Root | Fragment, + options: TypeGeneratorOptions, + ) => string, }; diff --git a/packages/relay-compiler/language/javascript/RelayFlowGenerator.js b/packages/relay-compiler/language/javascript/RelayFlowGenerator.js index 36814ee4bacb7..8d2252b80666f 100644 --- a/packages/relay-compiler/language/javascript/RelayFlowGenerator.js +++ b/packages/relay-compiler/language/javascript/RelayFlowGenerator.js @@ -19,7 +19,6 @@ const RelayMatchTransform = require('../../transforms/RelayMatchTransform'); const RelayRefetchableFragmentTransform = require('../../transforms/RelayRefetchableFragmentTransform'); const RelayRelayDirectiveTransform = require('../../transforms/RelayRelayDirectiveTransform'); -const {isAbstractType} = require('../../core/GraphQLSchemaUtils'); const {createUserError} = require('../../core/RelayCompilerError'); const { anyTypeAlias, @@ -40,16 +39,18 @@ const { } = require('./RelayFlowTypeTransformers'); import type {IRTransform} from '../../core/GraphQLCompilerContext'; -import type {Fragment, Root, Directive, Metadata} from '../../core/GraphQLIR'; +import type { + Fragment, + Root, + Directive, + Metadata, + ModuleImport, +} from '../../core/GraphQLIR'; +import type {Schema, TypeID} from '../../core/Schema'; import type {TypeGeneratorOptions} from '../RelayLanguagePluginInterface'; -import type {GraphQLEnumType} from 'graphql'; + const babelGenerator = require('@babel/generator').default; const t = require('@babel/types'); -const { - GraphQLInputObjectType, - GraphQLNonNull, - GraphQLString, -} = require('graphql'); const invariant = require('invariant'); const nullthrows = require('nullthrows'); @@ -59,19 +60,20 @@ export type State = {| ...TypeGeneratorOptions, +generatedFragments: Set, +generatedInputObjectTypes: { - [name: string]: GraphQLInputObjectType | 'pending', + [name: string]: TypeID | 'pending', }, hasConnectionResolver: boolean, - +usedEnums: {[name: string]: GraphQLEnumType}, + +usedEnums: {[name: string]: TypeID}, +usedFragments: Set, +matchFields: Map, |}; function generate( + schema: Schema, node: Root | Fragment, options: TypeGeneratorOptions, ): string { - const ast = IRVisitor.visit(node, createVisitor(options)); + const ast = IRVisitor.visit(node, createVisitor(schema, options)); return babelGenerator(ast).code; } @@ -79,7 +81,7 @@ type Selection = { key: string, schemaName?: string, value?: any, - nodeType?: any, + nodeType?: TypeID | 'MODULE_IMPORT_FIELD', conditional?: boolean, concreteType?: string, ref?: string, @@ -88,16 +90,19 @@ type Selection = { type SelectionMap = Map; function makeProp( + schema: Schema, {key, schemaName, value, conditional, nodeType, nodeSelections}: Selection, state: State, unmasked: boolean, concreteType?: string, ) { - if (nodeType) { + if (nodeType && nodeType !== 'MODULE_IMPORT_FIELD') { value = transformScalarType( + schema, nodeType, state, selectionsToBabel( + schema, [Array.from(nullthrows(nodeSelections).values())], state, unmasked, @@ -119,6 +124,7 @@ const hasTypenameSelection = selections => selections.some(isTypenameSelection); const onlySelectsTypename = selections => selections.every(isTypenameSelection); function selectionsToBabel( + schema: Schema, selections: $ReadOnlyArray<$ReadOnlyArray>, state: State, unmasked: boolean, @@ -126,7 +132,6 @@ function selectionsToBabel( ) { const baseFields = new Map(); const byConcreteType = {}; - flattenArray(selections).forEach(selection => { const {concreteType} = selection; if (concreteType) { @@ -162,7 +167,7 @@ function selectionsToBabel( if (selection.schemaName === '__typename') { typenameAliases.add(selection.key); } - return makeProp(selection, state, unmasked, concreteType); + return makeProp(schema, selection, state, unmasked, concreteType); }), ); } @@ -199,12 +204,13 @@ function selectionsToBabel( sel => isTypenameSelection(sel) && sel.concreteType ? makeProp( + schema, {...sel, conditional: false}, state, unmasked, sel.concreteType, ) - : makeProp(sel, state, unmasked), + : makeProp(schema, sel, state, unmasked), ); types.push(selectionMapValues); } @@ -272,7 +278,7 @@ function isPlural(node: Fragment): boolean { return Boolean(node.metadata && node.metadata.plural); } -function createVisitor(options: TypeGeneratorOptions) { +function createVisitor(schema: Schema, options: TypeGeneratorOptions) { const state = { customScalars: options.customScalars, enumsHasteModule: options.enumsHasteModule, @@ -291,11 +297,16 @@ function createVisitor(options: TypeGeneratorOptions) { return { leave: { Root(node) { - const inputVariablesType = generateInputVariablesType(node, state); + const inputVariablesType = generateInputVariablesType( + schema, + node, + state, + ); const inputObjectTypes = generateInputObjectTypes(state); const responseType = exportType( `${node.name}Response`, selectionsToBabel( + schema, /* $FlowFixMe: selections have already been transformed */ (node.selections: $ReadOnlyArray<$ReadOnlyArray>), state, @@ -323,7 +334,7 @@ function createVisitor(options: TypeGeneratorOptions) { ) { rawResponseType = IRVisitor.visit( normalizationIR, - createRawResponseTypeVisitor(state), + createRawResponseTypeVisitor(schema, state), ); } const refetchableFragmentName = getRefetchableQueryParentFragmentName( @@ -339,7 +350,7 @@ function createVisitor(options: TypeGeneratorOptions) { ? generateFragmentRefsForRefetchable(refetchableFragmentName) : getFragmentImports(state)), ...(importedTypes ? importTypes(importedTypes, 'relay-runtime') : []), - ...getEnumDefinitions(state), + ...getEnumDefinitions(schema, state), ...inputObjectTypes, inputVariablesType, responseType, @@ -375,12 +386,12 @@ function createVisitor(options: TypeGeneratorOptions) { if ( numConecreteSelections <= 1 && isTypenameSelection(selection) && - !isAbstractType(node.type) + !schema.isAbstractType(node.type) ) { return [ { ...selection, - concreteType: node.type.toString(), + concreteType: schema.getTypeString(node.type), }, ]; } @@ -415,6 +426,7 @@ function createVisitor(options: TypeGeneratorOptions) { const unmasked = node.metadata != null && node.metadata.mask === false; const baseType = selectionsToBabel( + schema, selections, state, unmasked, @@ -430,7 +442,7 @@ function createVisitor(options: TypeGeneratorOptions) { return t.program([ ...getFragmentImports(state), - ...getEnumDefinitions(state), + ...getEnumDefinitions(schema, state), importTypes(importedTypes.sort(), 'relay-runtime'), ...fragmentTypes, exportType(node.name, type), @@ -442,28 +454,27 @@ function createVisitor(options: TypeGeneratorOptions) { ]); }, InlineFragment(node) { - const typeCondition = node.typeCondition; return flattenArray( /* $FlowFixMe: selections have already been transformed */ (node.selections: $ReadOnlyArray<$ReadOnlyArray>), ).map(typeSelection => { - return isAbstractType(typeCondition) + return schema.isAbstractType(node.typeCondition) ? { ...typeSelection, conditional: true, } : { ...typeSelection, - concreteType: typeCondition.toString(), + concreteType: schema.getTypeString(node.typeCondition), }; }); }, Condition: visitCondition, ScalarField(node) { - return visitScalarField(node, state); + return visitScalarField(schema, node, state); }, Connection(node) { - return visitConnection(node, state); + return visitConnection(schema, node, state); }, ConnectionField: visitLinkedField, LinkedField: visitLinkedField, @@ -472,12 +483,20 @@ function createVisitor(options: TypeGeneratorOptions) { { key: '__fragmentPropName', conditional: true, - value: transformScalarType(GraphQLString, state), + value: transformScalarType( + schema, + schema.expectStringType(), + state, + ), }, { key: '__module_component', conditional: true, - value: transformScalarType(GraphQLString, state), + value: transformScalarType( + schema, + schema.expectStringType(), + state, + ), }, { key: '__fragments_' + node.name, @@ -510,20 +529,21 @@ function visitCondition(node, state) { }); } -function visitScalarField(node, state) { +function visitScalarField(schema, node, state) { return [ { key: node.alias, schemaName: node.name, - value: transformScalarType(node.type, state), + value: transformScalarType(schema, node.type, state), }, ]; } -function visitConnection(node, state) { +function visitConnection(schema, node, state) { state.hasConnectionResolver = true; + /* $FlowFixMe: selections have already been transformed */ - const babel = selectionsToBabel(node.selections, state, false, null); + const babel = selectionsToBabel(schema, node.selections, state, false, null); if ( babel == null || typeof babel !== 'object' || @@ -590,26 +610,31 @@ function visitLinkedField(node) { } function makeRawResponseProp( + schema: Schema, {key, schemaName, value, conditional, nodeType, nodeSelections}: Selection, state: State, concreteType: ?string, ) { - if (nodeType) { - if (nodeType === MODULE_IMPORT_FIELD) { - return t.objectTypeSpreadProperty( - t.genericTypeAnnotation(t.identifier(key)), - ); - } + if (nodeType === 'MODULE_IMPORT_FIELD') { + return t.objectTypeSpreadProperty( + t.genericTypeAnnotation(t.identifier(key)), + ); + } else if (nodeType) { value = transformScalarType( + schema, nodeType, state, selectionsToRawResponseBabel( + schema, [Array.from(nullthrows(nodeSelections).values())], state, - isAbstractType(nodeType) ? null : nodeType.name, + schema.isAbstractType(nodeType) || schema.isWrapper(nodeType) + ? null + : schema.getTypeString(nodeType), ), ); } + if (schemaName === '__typename' && concreteType) { value = t.stringLiteralTypeAnnotation(concreteType); } @@ -622,6 +647,7 @@ function makeRawResponseProp( // Trasform the codegen IR selections into Babel flow types function selectionsToRawResponseBabel( + schema: Schema, selections: $ReadOnlyArray<$ReadOnlyArray>, state: State, nodeTypeName: ?string, @@ -653,12 +679,13 @@ function selectionsToRawResponseBabel( ).map(selection => { if (isTypenameSelection(selection)) { return makeRawResponseProp( + schema, {...selection, conditional: false}, state, concreteType, ); } - return makeRawResponseProp(selection, state, concreteType); + return makeRawResponseProp(schema, selection, state, concreteType); }), ); } @@ -668,12 +695,13 @@ function selectionsToRawResponseBabel( baseFields.map(selection => { if (isTypenameSelection(selection)) { return makeRawResponseProp( + schema, {...selection, conditional: false}, state, nodeTypeName, ); } - return makeRawResponseProp(selection, state, null); + return makeRawResponseProp(schema, selection, state, null); }), ); } @@ -682,14 +710,15 @@ function selectionsToRawResponseBabel( ); } -// Visitor for generating raw reponse type -function createRawResponseTypeVisitor(state: State) { +// Visitor for generating raw response type +function createRawResponseTypeVisitor(schema: Schema, state: State) { const visitor = { leave: { Root(node) { return exportType( `${node.name}RawResponse`, selectionsToRawResponseBabel( + schema, /* $FlowFixMe: selections have already been transformed */ (node.selections: $ReadOnlyArray<$ReadOnlyArray>), state, @@ -703,20 +732,20 @@ function createRawResponseTypeVisitor(state: State) { /* $FlowFixMe: selections have already been transformed */ (node.selections: $ReadOnlyArray<$ReadOnlyArray>), ).map(typeSelection => { - return isAbstractType(typeCondition) + return schema.isAbstractType(typeCondition) ? typeSelection : { ...typeSelection, - concreteType: typeCondition.toString(), + concreteType: schema.getTypeString(typeCondition), }; }); }, Condition: visitCondition, ScalarField(node) { - return visitScalarField(node, state); + return visitScalarField(schema, node, state); }, Connection(node) { - return visitConnection(node, state); + return visitConnection(schema, node, state); }, ConnectionField: visitLinkedField, LinkedField: visitLinkedField, @@ -742,7 +771,7 @@ function createRawResponseTypeVisitor(state: State) { ); }, ModuleImport(node) { - return visitRawResposneModuleImport(node, state); + return visitRawResposneModuleImport(schema, node, state); }, FragmentSpread(node) { invariant( @@ -756,8 +785,12 @@ function createRawResponseTypeVisitor(state: State) { return visitor; } -// Dedupe the genreated type of module selections to reduce file zie -function visitRawResposneModuleImport(node, state): $ReadOnlyArray { +// Dedupe the generated type of module selections to reduce file size +function visitRawResposneModuleImport( + schema: Schema, + node: ModuleImport, + state: State, +): $ReadOnlyArray { const {selections, name: key} = node; const moduleSelections = selections .filter( @@ -767,6 +800,7 @@ function visitRawResposneModuleImport(node, state): $ReadOnlyArray { .map(arr => arr[0]); if (!state.matchFields.has(key)) { const ast = selectionsToRawResponseBabel( + schema, /* $FlowFixMe: selections have already been transformed */ (node.selections: $ReadOnlyArray<$ReadOnlyArray>).filter( sel => sel.length > 1 || sel[0].schemaName !== 'js', @@ -826,16 +860,16 @@ function generateInputObjectTypes(state: State) { }); } -function generateInputVariablesType(node: Root, state: State) { +function generateInputVariablesType(schema: Schema, node: Root, state: State) { return exportType( `${node.name}Variables`, exactObjectTypeAnnotation( node.argumentDefinitions.map(arg => { const property = t.objectTypeProperty( t.identifier(arg.name), - transformInputType(arg.type, state), + transformInputType(schema, arg.type, state), ); - if (!(arg.type instanceof GraphQLNonNull)) { + if (!schema.isNonNull(arg.type)) { property.optional = true; } return property; @@ -898,11 +932,10 @@ function getFragmentImports(state: State) { return imports; } -function getEnumDefinitions({ - enumsHasteModule, - usedEnums, - noFutureProofEnums, -}: State) { +function getEnumDefinitions( + schema: Schema, + {enumsHasteModule, usedEnums, noFutureProofEnums}: State, +) { const enumNames = Object.keys(usedEnums).sort(); if (enumNames.length === 0) { return []; @@ -916,7 +949,7 @@ function getEnumDefinitions({ ); } return enumNames.map(name => { - const values = usedEnums[name].getValues().map(({value}) => value); + const values = [].concat(schema.getEnumValues(usedEnums[name])); values.sort(); if (!noFutureProofEnums) { values.push('%future added value'); @@ -1036,6 +1069,7 @@ const DIRECTIVE_NAME = 'raw_response_type'; module.exports = { generate: (Profiler.instrument(generate, 'RelayFlowGenerator.generate'): ( + schema: Schema, node: Root | Fragment, options: TypeGeneratorOptions, ) => string), diff --git a/packages/relay-compiler/language/javascript/RelayFlowTypeTransformers.js b/packages/relay-compiler/language/javascript/RelayFlowTypeTransformers.js index 5686d111376dc..45df144e0f931 100644 --- a/packages/relay-compiler/language/javascript/RelayFlowTypeTransformers.js +++ b/packages/relay-compiler/language/javascript/RelayFlowTypeTransformers.js @@ -16,70 +16,72 @@ const { exactObjectTypeAnnotation, readOnlyArrayOfType, } = require('./RelayFlowBabelFactories'); -const { - GraphQLEnumType, - GraphQLInputObjectType, - GraphQLInterfaceType, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLScalarType, - GraphQLUnionType, -} = require('graphql'); + +import type {Schema, TypeID} from '../../core/Schema'; export type ScalarTypeMapping = { [type: string]: string, }; -import type {GraphQLInputType, GraphQLType} from 'graphql'; - import type {State} from './RelayFlowGenerator'; -function getInputObjectTypeIdentifier(type: GraphQLInputObjectType): string { - return type.name; +function getInputObjectTypeIdentifier(schema: Schema, typeID: TypeID): string { + return schema.getTypeString(typeID); } function transformScalarType( - type: GraphQLType, + schema: Schema, + type: TypeID, state: State, objectProps?: mixed, ): mixed { - if (type instanceof GraphQLNonNull) { - return transformNonNullableScalarType(type.ofType, state, objectProps); + if (schema.isNonNull(type)) { + return transformNonNullableScalarType( + schema, + schema.getNullableType(type), + state, + objectProps, + ); } else { return t.nullableTypeAnnotation( - transformNonNullableScalarType(type, state, objectProps), + transformNonNullableScalarType(schema, type, state, objectProps), ); } } function transformNonNullableScalarType( - type: GraphQLType, + schema: Schema, + type: TypeID, state: State, objectProps, ) { - if (type instanceof GraphQLList) { + if (schema.isList(type)) { return readOnlyArrayOfType( - transformScalarType(type.ofType, state, objectProps), + transformScalarType( + schema, + schema.getNonListType(type), + state, + objectProps, + ), ); } else if ( - type instanceof GraphQLObjectType || - type instanceof GraphQLUnionType || - type instanceof GraphQLInterfaceType + schema.isObject(type) || + schema.isUnion(type) || + schema.isInterface(type) ) { return objectProps; - } else if (type instanceof GraphQLScalarType) { - return transformGraphQLScalarType(type, state); - } else if (type instanceof GraphQLEnumType) { - return transformGraphQLEnumType(type, state); + } else if (schema.isScalar(type)) { + return transformGraphQLScalarType(schema.getTypeString(type), state); + } else if (schema.isEnum(type)) { + return transformGraphQLEnumType(schema, type, state); } else { - throw new Error(`Could not convert from GraphQL type ${type.toString()}`); + throw new Error(`Could not convert from GraphQL type ${String(type)}`); } } -function transformGraphQLScalarType(type: GraphQLScalarType, state: State) { - const customType = state.customScalars[type.name]; - switch (customType || type.name) { +function transformGraphQLScalarType(typeName: string, state: State) { + const customType = state.customScalars[typeName]; + switch (customType ?? typeName) { case 'ID': case 'String': return t.stringTypeAnnotation(); @@ -99,54 +101,71 @@ function transformGraphQLScalarType(type: GraphQLScalarType, state: State) { } } -function transformGraphQLEnumType(type: GraphQLEnumType, state: State) { - state.usedEnums[type.name] = type; - return t.genericTypeAnnotation(t.identifier(type.name)); +function transformGraphQLEnumType(schema: Schema, type: TypeID, state: State) { + state.usedEnums[schema.getTypeString(type)] = type; + return t.genericTypeAnnotation(t.identifier(schema.getTypeString(type))); } -function transformInputType(type: GraphQLInputType, state: State): $FlowFixMe { - if (type instanceof GraphQLNonNull) { - return transformNonNullableInputType(type.ofType, state); +function transformInputType( + schema: Schema, + type: TypeID, + state: State, +): $FlowFixMe { + if (schema.isNonNull(type)) { + return transformNonNullableInputType( + schema, + schema.getNullableType(type), + state, + ); } else { - return t.nullableTypeAnnotation(transformNonNullableInputType(type, state)); + return t.nullableTypeAnnotation( + transformNonNullableInputType(schema, type, state), + ); } } -function transformNonNullableInputType(type: GraphQLInputType, state: State) { - if (type instanceof GraphQLList) { - return readOnlyArrayOfType(transformInputType(type.ofType, state)); - } else if (type instanceof GraphQLScalarType) { - return transformGraphQLScalarType(type, state); - } else if (type instanceof GraphQLEnumType) { - return transformGraphQLEnumType(type, state); - } else if (type instanceof GraphQLInputObjectType) { - const typeIdentifier = getInputObjectTypeIdentifier(type); +function transformNonNullableInputType( + schema: Schema, + typeID: TypeID, + state: State, +) { + if (schema.isList(typeID)) { + return readOnlyArrayOfType( + transformInputType(schema, schema.getNonListType(typeID), state), + ); + } else if (schema.isScalar(typeID)) { + return transformGraphQLScalarType(schema.getTypeString(typeID), state); + } else if (schema.isEnum(typeID)) { + return transformGraphQLEnumType(schema, typeID, state); + } else if (schema.isInput(typeID)) { + const typeIdentifier = getInputObjectTypeIdentifier(schema, typeID); if (state.generatedInputObjectTypes[typeIdentifier]) { return t.genericTypeAnnotation(t.identifier(typeIdentifier)); } state.generatedInputObjectTypes[typeIdentifier] = 'pending'; - const fields = type.getFields(); - const props = Object.keys(fields) - .map(key => fields[key]) - .map(field => { - const property = t.objectTypeProperty( - t.identifier(field.name), - transformInputType(field.type, state), - ); - if ( - state.optionalInputFields.indexOf(field.name) >= 0 || - !(field.type instanceof GraphQLNonNull) - ) { - property.optional = true; - } - return property; - }); + const fields = schema.getFields(typeID); + const props = fields.map(fieldID => { + const fieldType = schema.getFieldType(fieldID); + const fieldName = schema.getFieldName(fieldID); + const property = t.objectTypeProperty( + t.identifier(fieldName), + transformInputType(schema, fieldType, state), + ); + if ( + state.optionalInputFields.indexOf(fieldName) >= 0 || + !schema.isNonNull(fieldType) + ) { + property.optional = true; + } + return property; + }); + state.generatedInputObjectTypes[typeIdentifier] = exactObjectTypeAnnotation( props, ); return t.genericTypeAnnotation(t.identifier(typeIdentifier)); } else { - throw new Error(`Could not convert from GraphQL type ${type.toString()}`); + throw new Error(`Could not convert from GraphQL type ${String(typeID)}`); } } diff --git a/packages/relay-compiler/language/javascript/__tests__/RelayFlowGenerator-test.js b/packages/relay-compiler/language/javascript/__tests__/RelayFlowGenerator-test.js index 4a88a81a6f723..3e293675393f7 100644 --- a/packages/relay-compiler/language/javascript/__tests__/RelayFlowGenerator-test.js +++ b/packages/relay-compiler/language/javascript/__tests__/RelayFlowGenerator-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../../core/GraphQLCompilerContext'); const RelayFlowGenerator = require('../RelayFlowGenerator'); const RelayIRTransforms = require('../../../core/RelayIRTransforms'); +const Schema = require('../../../core/Schema'); const {transformASTSchema} = require('../../../core/ASTConvert'); const { @@ -25,7 +26,7 @@ const { import type {TypeGeneratorOptions} from '../../RelayLanguagePluginInterface'; function generate(text, options: TypeGeneratorOptions, context?) { - const schema = transformASTSchema(TestSchema, [ + const relaySchema = transformASTSchema(TestSchema, [ ...RelayIRTransforms.schemaExtensions, ` scalar Color @@ -34,30 +35,43 @@ function generate(text, options: TypeGeneratorOptions, context?) { } `, ]); - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(relaySchema, text); + const compilerSchema = Schema.DEPRECATED__create(TestSchema, relaySchema); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms(RelayFlowGenerator.transforms) .documents() .map( doc => - // $FlowFixMe - `SplitOperation` is incompatible with union type. - `// ${doc.name}.graphql\n${RelayFlowGenerator.generate(doc, { - ...options, - normalizationIR: context ? context.get(doc.name) : undefined, - })}`, + `// ${doc.name}.graphql\n${RelayFlowGenerator.generate( + compilerSchema, + // $FlowFixMe - `SplitOperation` is incompatible with union type. + doc, + // $FlowFixMe - `SplitOperation` is incompatible with union type. + { + ...options, + normalizationIR: context ? context.get(doc.name) : undefined, + }, + )}`, ) .join('\n\n'); } describe('Snapshot tests', () => { function generateContext(text) { - const schema = transformASTSchema( + const relaySchema = transformASTSchema( TestSchema, RelayIRTransforms.schemaExtensions, ); - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions, schema: extendedSchema} = parseGraphQLText( + relaySchema, + text, + ); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ ...RelayIRTransforms.commonTransforms, diff --git a/packages/relay-compiler/transforms/ClientExtensionsTransform.js b/packages/relay-compiler/transforms/ClientExtensionsTransform.js index ec65630072fee..96e30abfc2219 100644 --- a/packages/relay-compiler/transforms/ClientExtensionsTransform.js +++ b/packages/relay-compiler/transforms/ClientExtensionsTransform.js @@ -12,10 +12,6 @@ const GraphQLIRTransformer = require('../core/GraphQLIRTransformer'); -const { - getRawType, - isClientDefinedField, -} = require('../core/GraphQLSchemaUtils'); const { createCompilerError, createUserError, @@ -23,19 +19,14 @@ const { import type GraphQLCompilerContext from '../core/GraphQLCompilerContext'; import type {Definition, Node, Selection} from '../core/GraphQLIR'; -import type {GraphQLType} from 'graphql'; - -type State = {| - clientFields: Map, - parentType: GraphQLType | null, -|}; +import type {TypeID} from '../core/Schema'; let cachesByNode = new Map(); function clientExtensionTransform( context: GraphQLCompilerContext, ): GraphQLCompilerContext { cachesByNode = new Map(); - return GraphQLIRTransformer.transform(context, { + return GraphQLIRTransformer.transform(context, { Fragment: traverseDefinition, Root: traverseDefinition, SplitOperation: traverseDefinition, @@ -43,32 +34,39 @@ function clientExtensionTransform( } function traverseDefinition(node: T): T { - const compilerContext = this.getContext(); - const {serverSchema, clientSchema} = compilerContext; + const compilerContext: GraphQLCompilerContext = this.getContext(); + + const schema = compilerContext.getSchema(); + let rootType; switch (node.kind) { case 'Root': switch (node.operation) { case 'query': - rootType = serverSchema.getQueryType(); + rootType = schema.getQueryType(); break; case 'mutation': - rootType = serverSchema.getMutationType(); + rootType = schema.getMutationType(); break; case 'subscription': - rootType = serverSchema.getSubscriptionType(); + rootType = schema.getSubscriptionType(); break; default: (node.operation: empty); } break; case 'SplitOperation': - rootType = serverSchema.getType(node.type.name); + if (!schema.isServerType(node.type)) { + throw createUserError( + 'ClientExtensionTransform: SplitOperation (@module) can be created ' + + 'only for fragments that defined on a server type', + [node.loc], + ); + } + rootType = node.type; break; case 'Fragment': - rootType = - serverSchema.getType(node.type.name) ?? - clientSchema.getType(node.type.name); + rootType = node.type; break; default: (node: empty); @@ -88,7 +86,7 @@ function traverseDefinition(node: T): T { function traverseSelections( node: T, compilerContext: GraphQLCompilerContext, - parentType: GraphQLType, + parentType: TypeID, ): T { let nodeCache = cachesByNode.get(node); if (nodeCache == null) { @@ -100,7 +98,8 @@ function traverseSelections( // $FlowFixMe - TODO: type IRTransformer to allow changing result type return result; } - const {serverSchema, clientSchema} = compilerContext; + const schema = compilerContext.getSchema(); + const clientSelections = []; const serverSelections = cowMap(node.selections, selection => { switch (selection.kind) { @@ -117,7 +116,9 @@ function traverseSelections( case 'Stream': return traverseSelections(selection, compilerContext, parentType); case 'ScalarField': - if (isClientDefinedField(selection, compilerContext, parentType)) { + if ( + schema.isClientDefinedField(schema.getRawType(parentType), selection) + ) { clientSelections.push(selection); return null; } else { @@ -125,52 +126,33 @@ function traverseSelections( } case 'ConnectionField': case 'LinkedField': { - if (isClientDefinedField(selection, compilerContext, parentType)) { + if ( + schema.isClientDefinedField(schema.getRawType(parentType), selection) + ) { clientSelections.push(selection); return null; } - const rawType = getRawType(selection.type); - const fieldType = - serverSchema.getType(rawType.name) ?? - clientSchema.getType(rawType.name); - if (fieldType == null) { - throw createCompilerError( - 'ClientExtensionTransform: Expected to be able to determine ' + - `type of field \`${selection.name}\`.`, - [selection.loc], - ); - } - return traverseSelections(selection, compilerContext, fieldType); + return traverseSelections(selection, compilerContext, selection.type); } case 'InlineFragment': { - const typeName = selection.typeCondition.name; - const serverType = serverSchema.getType(typeName); - const clientType = clientSchema.getType(typeName); - const isClientType = serverType == null && clientType != null; + const isClientType = !schema.isServerType(selection.typeCondition); if (isClientType) { clientSelections.push(selection); return null; } - const type = serverType ?? clientType; - if (type == null) { - throw createCompilerError( - 'ClientExtensionTransform: Expected to be able to determine ' + - `type of inline fragment on \`${typeName}\`.`, - [selection.loc], - ); - } - return traverseSelections(selection, compilerContext, type); + return traverseSelections( + selection, + compilerContext, + selection.typeCondition, + ); } case 'FragmentSpread': { const fragment = compilerContext.getFragment( selection.name, selection.loc, ); - const typeName = fragment.type.name; - const serverType = serverSchema.getType(typeName); - const clientType = clientSchema.getType(typeName); - const isClientType = serverType == null && clientType != null; + const isClientType = !schema.isServerType(fragment.type); if (isClientType) { clientSelections.push(selection); diff --git a/packages/relay-compiler/transforms/ConnectionFieldTransform.js b/packages/relay-compiler/transforms/ConnectionFieldTransform.js index 25ec11d0bbb3b..e7fbbb634de84 100644 --- a/packages/relay-compiler/transforms/ConnectionFieldTransform.js +++ b/packages/relay-compiler/transforms/ConnectionFieldTransform.js @@ -12,19 +12,10 @@ const IRTransformer = require('../core/GraphQLIRTransformer'); -const {getNullableType, getRawType} = require('../core/GraphQLSchemaUtils'); const {createUserError} = require('../core/RelayCompilerError'); const { buildConnectionMetadata, } = require('../handlers/connection/RelayConnectionTransform'); -const { - GraphQLID, - GraphQLInterfaceType, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLUnionType, -} = require('graphql'); const {ConnectionInterface} = require('relay-runtime'); import type CompilerContext from '../core/GraphQLCompilerContext'; @@ -98,6 +89,8 @@ function visitLinkedField( field: LinkedField, state: State, ): LinkedField | ConnectionField { + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); const path = state.path.concat(field.alias); const transformed: LinkedField = this.traverse(field, { ...state, @@ -111,10 +104,10 @@ function visitLinkedField( if (connectionDirective == null) { return transformed; } - if (getNullableType(transformed.type) instanceof GraphQLList) { + if (schema.isList(schema.getNullableType(transformed.type))) { throw createUserError( "@connection_resolver fields must return a single value, not a list, found '" + - `${String(transformed.type)}'`, + `${schema.getTypeString(transformed.type)}'`, [transformed.loc], ); } @@ -215,25 +208,29 @@ function visitLinkedField( [connectionDirective.loc], ); } - const connectionType = getRawType(transformed.type); - const edgesFieldDef = - connectionType instanceof GraphQLObjectType - ? connectionType.getFields().edges - : null; + const connectionType = schema.getRawType(transformed.type); + const edgesFieldDef = schema.isObject(connectionType) + ? schema.getFieldByName(connectionType, 'edges') + : null; const edgesType = - edgesFieldDef != null ? getRawType(edgesFieldDef.type) : null; + edgesFieldDef != null + ? schema.getRawType(schema.getFieldType(edgesFieldDef)) + : null; const nodeFieldDef = - edgesType != null && edgesType instanceof GraphQLObjectType - ? edgesType.getFields().node + edgesType != null && schema.isObject(edgesType) + ? schema.getFieldByName(edgesType, 'node') + : null; + const nodeType = + nodeFieldDef != null + ? schema.getRawType(schema.getFieldType(nodeFieldDef)) : null; - const nodeType = nodeFieldDef != null ? getRawType(nodeFieldDef.type) : null; if ( edgesType == null || nodeType == null || !( - nodeType instanceof GraphQLInterfaceType || - nodeType instanceof GraphQLObjectType || - nodeType instanceof GraphQLUnionType + schema.isObject(nodeType) || + schema.isInterface(nodeType) || + schema.isUnion(nodeType) ) ) { throw createUserError( @@ -255,7 +252,7 @@ function visitLinkedField( loc: edgeField.loc, metadata: null, name: '__id', - type: new GraphQLNonNull(GraphQLID), + type: schema.getNonNullType(schema.expectIdType()), }, { alias: 'node', @@ -277,7 +274,7 @@ function visitLinkedField( loc: edgeField.loc, metadata: null, name: '__id', - type: new GraphQLNonNull(GraphQLID), + type: schema.getNonNullType(schema.expectIdType()), }, ], type: nodeType, diff --git a/packages/relay-compiler/transforms/FilterDirectivesTransform.js b/packages/relay-compiler/transforms/FilterDirectivesTransform.js index c8e164a0f39e8..b9bcb1cca0edc 100644 --- a/packages/relay-compiler/transforms/FilterDirectivesTransform.js +++ b/packages/relay-compiler/transforms/FilterDirectivesTransform.js @@ -23,8 +23,10 @@ function filterDirectivesTransform( context: GraphQLCompilerContext, ): GraphQLCompilerContext { const schemaDirectives = new Set( - context.serverSchema + context + .getSchema() .getDirectives() + .filter(directive => !directive.clientOnlyDirective) .map(schemaDirective => schemaDirective.name), ); const visitDirective = (directive: Directive): ?Directive => { diff --git a/packages/relay-compiler/transforms/FlattenTransform.js b/packages/relay-compiler/transforms/FlattenTransform.js index 44e8bd82eaf46..ca30b8d83cb1e 100644 --- a/packages/relay-compiler/transforms/FlattenTransform.js +++ b/packages/relay-compiler/transforms/FlattenTransform.js @@ -11,7 +11,6 @@ 'use strict'; const GraphQLIRTransformer = require('../core/GraphQLIRTransformer'); -const GraphQLSchemaUtils = require('../core/GraphQLSchemaUtils'); const areEqual = require('../util/areEqualOSS'); const getIdentifierForSelection = require('../core/getIdentifierForSelection'); @@ -32,9 +31,7 @@ import type { ScalarField, Selection, } from '../core/GraphQLIR'; -import type {GraphQLType} from 'graphql'; - -const {getRawType, isAbstractType} = GraphQLSchemaUtils; +import type {Schema, TypeID} from '../core/Schema'; export type FlattenOptions = { flattenAbstractTypes?: boolean, @@ -42,7 +39,7 @@ export type FlattenOptions = { type State = { flattenAbstractTypes: boolean, - parentType: ?GraphQLType, + parentType: ?TypeID, }; /** @@ -82,6 +79,7 @@ function flattenTransformImpl( function memoizedFlattenSelection(cache) { return function flattenSelectionsFn(node: T, state: State): T { + const context: GraphQLCompilerContext = this.getContext(); let nodeCache = cache.get(node); if (nodeCache == null) { nodeCache = new Map(); @@ -114,6 +112,7 @@ function memoizedFlattenSelection(cache) { // keeping track of the parent type. const nextSelections = new Map(); const hasFlattened = flattenSelectionsInto( + context.getSchema(), nextSelections, node, state, @@ -134,22 +133,29 @@ function memoizedFlattenSelection(cache) { * @private */ function flattenSelectionsInto( + schema: Schema, flattenedSelections: Map, node: Node, state: State, - type: GraphQLType, + type: TypeID, ): boolean { let hasFlattened = false; node.selections.forEach(selection => { if ( selection.kind === 'InlineFragment' && - shouldFlattenInlineFragment(selection, state, type) + shouldFlattenInlineFragment(schema, selection, state, type) ) { hasFlattened = true; - flattenSelectionsInto(flattenedSelections, selection, state, type); + flattenSelectionsInto( + schema, + flattenedSelections, + selection, + state, + type, + ); return; } - const nodeIdentifier = getIdentifierForSelection(selection); + const nodeIdentifier = getIdentifierForSelection(schema, selection); const flattenedSelection = flattenedSelections.get(nodeIdentifier); // If this selection hasn't been seen before, keep track of it. if (!flattenedSelection) { @@ -170,6 +176,7 @@ function flattenSelectionsInto( flattenedSelections.set(nodeIdentifier, { ...flattenedSelection, selections: mergeSelections( + schema, flattenedSelection, selection, state, @@ -185,7 +192,13 @@ function flattenSelectionsInto( } flattenedSelections.set(nodeIdentifier, { ...flattenedSelection, - selections: mergeSelections(flattenedSelection, selection, state, type), + selections: mergeSelections( + schema, + flattenedSelection, + selection, + state, + type, + ), }); } else if (flattenedSelection.kind === 'ClientExtension') { if (selection.kind !== 'ClientExtension') { @@ -198,7 +211,13 @@ function flattenSelectionsInto( } flattenedSelections.set(nodeIdentifier, { ...flattenedSelection, - selections: mergeSelections(flattenedSelection, selection, state, type), + selections: mergeSelections( + schema, + flattenedSelection, + selection, + state, + type, + ), }); } else if (flattenedSelection.kind === 'FragmentSpread') { // Ignore duplicate fragment spreads. @@ -224,7 +243,13 @@ function flattenSelectionsInto( } flattenedSelections.set(nodeIdentifier, { ...flattenedSelection, - selections: mergeSelections(flattenedSelection, selection, state, type), + selections: mergeSelections( + schema, + flattenedSelection, + selection, + state, + type, + ), }); } else if (flattenedSelection.kind === 'Defer') { if (selection.kind !== 'Defer') { @@ -236,7 +261,13 @@ function flattenSelectionsInto( flattenedSelections.set(nodeIdentifier, { kind: 'Defer', ...flattenedSelection, - selections: mergeSelections(flattenedSelection, selection, state, type), + selections: mergeSelections( + schema, + flattenedSelection, + selection, + state, + type, + ), }); } else if (flattenedSelection.kind === 'Stream') { if (selection.kind !== 'Stream') { @@ -248,7 +279,13 @@ function flattenSelectionsInto( flattenedSelections.set(nodeIdentifier, { kind: 'Stream', ...flattenedSelection, - selections: mergeSelections(flattenedSelection, selection, state, type), + selections: mergeSelections( + schema, + flattenedSelection, + selection, + state, + type, + ), }); } else if (flattenedSelection.kind === 'LinkedField') { if (selection.kind !== 'LinkedField') { @@ -270,6 +307,7 @@ function flattenSelectionsInto( metadata: flattenedSelection.metadata, name: flattenedSelection.name, selections: mergeSelections( + schema, flattenedSelection, selection, state, @@ -312,6 +350,7 @@ function flattenSelectionsInto( metadata: flattenedSelection.metadata, name: flattenedSelection.name, selections: mergeSelections( + schema, flattenedSelection, selection, state, @@ -337,6 +376,7 @@ function flattenSelectionsInto( kind: 'Connection', ...flattenedSelection, selections: mergeSelections( + schema, flattenedSelection, selection, state, @@ -357,14 +397,15 @@ function flattenSelectionsInto( * @private */ function mergeSelections( + schema: Schema, nodeA: Node, nodeB: Node, state: State, - type: GraphQLType, + type: TypeID, ): $ReadOnlyArray { const flattenedSelections = new Map(); - flattenSelectionsInto(flattenedSelections, nodeA, state, type); - flattenSelectionsInto(flattenedSelections, nodeB, state, type); + flattenSelectionsInto(schema, flattenedSelections, nodeA, state, type); + flattenSelectionsInto(schema, flattenedSelections, nodeB, state, type); return Array.from(flattenedSelections.values()); } @@ -387,13 +428,15 @@ function assertUniqueArgsForAlias(field: Field, otherField: Field): void { * @private */ function shouldFlattenInlineFragment( + schema: Schema, fragment: InlineFragment, state: State, - type: GraphQLType, + type: TypeID, ): boolean { return ( - fragment.typeCondition.name === getRawType(type).name || - (state.flattenAbstractTypes && isAbstractType(fragment.typeCondition)) + schema.areEqualTypes(fragment.typeCondition, schema.getRawType(type)) || + (state.flattenAbstractTypes && + schema.isAbstractType(fragment.typeCondition)) ); } diff --git a/packages/relay-compiler/transforms/InlineFragmentsTransform.js b/packages/relay-compiler/transforms/InlineFragmentsTransform.js index 1f37a64ed0065..3242f3d77ef78 100644 --- a/packages/relay-compiler/transforms/InlineFragmentsTransform.js +++ b/packages/relay-compiler/transforms/InlineFragmentsTransform.js @@ -48,7 +48,7 @@ function fragmentSpreadVisitor(cache: FragmentVisitorCache): FragmentVisitor { 'arguments. Use the `ApplyFragmentArgumentTransform` before flattening', fragmentSpread.name, ); - const fragment = this.getContext().getFragment( + const fragment: Fragment = this.getContext().getFragment( fragmentSpread.name, fragmentSpread.loc, ); diff --git a/packages/relay-compiler/transforms/RelayApplyFragmentArgumentTransform.js b/packages/relay-compiler/transforms/RelayApplyFragmentArgumentTransform.js index 334cec4029f8b..9dc50a6401ce5 100644 --- a/packages/relay-compiler/transforms/RelayApplyFragmentArgumentTransform.js +++ b/packages/relay-compiler/transforms/RelayApplyFragmentArgumentTransform.js @@ -430,6 +430,7 @@ function transformFragment( args: $ReadOnlyArray, errorContext: $ReadOnlyArray, ): ?Fragment { + const schema = context.getSchema(); const fragment = context.getFragment(spread.name, spread.loc); const argumentsHash = hashArguments(args, parentScope, errorContext); const fragmentName = argumentsHash @@ -450,6 +451,7 @@ function transformFragment( } } const fragmentScope = getFragmentScope( + schema, fragment.argumentDefinitions, args, parentScope, diff --git a/packages/relay-compiler/transforms/RelayDeferStreamTransform.js b/packages/relay-compiler/transforms/RelayDeferStreamTransform.js index a07728c84fb50..43a7cad678878 100644 --- a/packages/relay-compiler/transforms/RelayDeferStreamTransform.js +++ b/packages/relay-compiler/transforms/RelayDeferStreamTransform.js @@ -12,9 +12,7 @@ const IRTransformer = require('../core/GraphQLIRTransformer'); -const {getNullableType} = require('../core/GraphQLSchemaUtils'); const {createUserError} = require('../core/RelayCompilerError'); -const {GraphQLList} = require('graphql'); const {ConnectionInterface} = require('relay-runtime'); import type CompilerContext from '../core/GraphQLCompilerContext'; @@ -151,6 +149,9 @@ function visitLinkedField( field: LinkedField, state: State, ): LinkedField | Stream { + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); + let transformedField: LinkedField = this.traverse(field, state); const streamDirective = transformedField.directives.find( directive => directive.name === 'stream', @@ -158,8 +159,8 @@ function visitLinkedField( if (streamDirective == null) { return transformedField; } - const type = getNullableType(field.type); - if (!(type instanceof GraphQLList)) { + const type = schema.getNullableType(field.type); + if (!schema.isList(type)) { throw createUserError( `Invalid use of @stream on non-plural field '${field.name}'`, [streamDirective.loc], @@ -216,7 +217,10 @@ function visitInlineFragment( fragment: InlineFragment, state: State, ): InlineFragment | Defer { - let transformedFragment = this.traverse(fragment, state); + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); + + let transformedFragment: InlineFragment = this.traverse(fragment, state); const deferDirective = transformedFragment.directives.find( directive => directive.name === 'defer', ); @@ -235,7 +239,8 @@ function visitInlineFragment( } const label = getLiteralStringArgument(deferDirective, 'label') ?? - fragment.typeCondition.name; + schema.getTypeString(fragment.typeCondition); + const transformedLabel = transformLabel(state.documentName, 'defer', label); state.recordLabel(transformedLabel, deferDirective); return { diff --git a/packages/relay-compiler/transforms/RelayFieldHandleTransform.js b/packages/relay-compiler/transforms/RelayFieldHandleTransform.js index 37090f2ddb1ed..8c1372440db59 100644 --- a/packages/relay-compiler/transforms/RelayFieldHandleTransform.js +++ b/packages/relay-compiler/transforms/RelayFieldHandleTransform.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict-local * @format */ @@ -13,8 +13,8 @@ const IRTransformer = require('../core/GraphQLIRTransformer'); const invariant = require('invariant'); +const nullthrows = require('nullthrows'); -const {GraphQLString} = require('graphql'); const {getRelayHandleKey} = require('relay-runtime'); import type CompilerContext from '../core/GraphQLCompilerContext'; @@ -31,12 +31,10 @@ function relayFieldHandleTransform(context: CompilerContext): CompilerContext { * @internal */ function visitField(field: F): F { - if (field.kind === 'LinkedField') { - field = this.traverse(field); - } - const handles = field.handles; + const nextField = field.kind === 'LinkedField' ? this.traverse(field) : field; + const handles = nextField.handles; if (!handles || !handles.length) { - return field; + return nextField; } // ensure exactly one handle invariant( @@ -45,12 +43,13 @@ function visitField(field: F): F { '"handle" property, got `%s`.', handles.join(', '), ); - const alias = field.alias; + const context: CompilerContext = this.getContext(); + const alias = nextField.alias; const handle = handles[0]; - const name = getRelayHandleKey(handle.name, handle.key, field.name); + const name = getRelayHandleKey(handle.name, handle.key, nextField.name); const filters = handle.filters; const args = filters - ? field.args.filter(arg => filters.indexOf(arg.name) !== -1) + ? nextField.args.filter(arg => filters.indexOf(arg.name) !== -1) : []; // T45504512: new connection model if (handle.dynamicKey != null) { @@ -58,13 +57,13 @@ function visitField(field: F): F { kind: 'Argument', loc: handle.dynamicKey.loc, name: '__dynamicKey', - type: GraphQLString, - value: handle.dynamicKey, + type: context.getSchema().expectStringType(), + value: nullthrows(handle.dynamicKey), }); } return ({ - ...field, + ...nextField, args, alias, name, diff --git a/packages/relay-compiler/transforms/RelayGenerateIDFieldTransform.js b/packages/relay-compiler/transforms/RelayGenerateIDFieldTransform.js index e588c69321747..03df0322bbf22 100644 --- a/packages/relay-compiler/transforms/RelayGenerateIDFieldTransform.js +++ b/packages/relay-compiler/transforms/RelayGenerateIDFieldTransform.js @@ -11,30 +11,16 @@ 'use strict'; const IRTransformer = require('../core/GraphQLIRTransformer'); -const SchemaUtils = require('../core/GraphQLSchemaUtils'); +const SchemaUtils = require('../core/SchemaUtils'); const {hasUnaliasedSelection} = require('./RelayTransformUtils'); -const { - assertAbstractType, - assertCompositeType, - assertLeafType, -} = require('graphql'); import type CompilerContext from '../core/GraphQLCompilerContext'; import type {InlineFragment, LinkedField, ScalarField} from '../core/GraphQLIR'; -import type {GraphQLCompositeType} from 'graphql'; -const { - canHaveSelections, - getRawType, - generateIDField, - hasID, - implementsInterface, - isAbstractType, - mayImplement, -} = SchemaUtils; +import type {TypeID} from '../core/Schema'; +const {generateIDField} = SchemaUtils; const ID = 'id'; -const ID_TYPE = 'ID'; const NODE_TYPE = 'Node'; type State = { @@ -48,8 +34,9 @@ type State = { function relayGenerateIDFieldTransform( context: CompilerContext, ): CompilerContext { - const idType = assertLeafType(context.serverSchema.getType(ID_TYPE)); - const idField: ScalarField = generateIDField(idType); + const schema = context.getSchema(); + const idType = schema.assertLeafType(schema.expectIdType()); + const idField = generateIDField(idType); const state = { idField, }; @@ -70,12 +57,17 @@ function visitLinkedField(field: LinkedField, state: State): LinkedField { return transformedNode; } - const context = this.getContext(); - const schema = context.serverSchema; - const unmodifiedType = assertCompositeType(getRawType(field.type)); + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); + const unmodifiedType = schema.assertCompositeType( + schema.getRawType(field.type), + ); // If the field type has an `id` subfield add an `id` selection - if (canHaveSelections(unmodifiedType) && hasID(schema, unmodifiedType)) { + if ( + schema.canHaveSelections(unmodifiedType) && + schema.hasId(unmodifiedType) + ) { return { ...transformedNode, selections: [...transformedNode.selections, state.idField], @@ -86,17 +78,23 @@ function visitLinkedField(field: LinkedField, state: State): LinkedField { // fragment if *any* concrete type implements Node. Then generate a // `... on PossibleType { id }` for every concrete type that does *not* // implement `Node` - if (isAbstractType(unmodifiedType)) { + const nodeType = schema.getTypeFromString(NODE_TYPE); + if (!nodeType) { + return transformedNode; + } + + if (schema.isAbstractType(unmodifiedType)) { const selections = [...transformedNode.selections]; - if (mayImplement(schema, unmodifiedType, NODE_TYPE)) { - const nodeType = assertCompositeType(schema.getType(NODE_TYPE)); + if (schema.mayImplement(unmodifiedType, nodeType)) { selections.push(buildIDFragment(nodeType, state.idField)); } - const abstractType = assertAbstractType(unmodifiedType); - schema.getPossibleTypes(abstractType).forEach(possibleType => { + schema.getPossibleTypes(unmodifiedType).forEach((possibleType: TypeID) => { if ( - !implementsInterface(possibleType, NODE_TYPE) && - hasID(schema, possibleType) + !schema.implementsInterface( + possibleType, + schema.expectTypeFromString(NODE_TYPE), + ) && + schema.hasId(possibleType) ) { selections.push(buildIDFragment(possibleType, state.idField)); } @@ -116,7 +114,7 @@ function visitLinkedField(field: LinkedField, state: State): LinkedField { * Returns IR for `... on FRAGMENT_TYPE { id }` */ function buildIDFragment( - fragmentType: GraphQLCompositeType, + fragmentType: TypeID, idField: ScalarField, ): InlineFragment { return { @@ -124,8 +122,8 @@ function buildIDFragment( directives: [], loc: {kind: 'Generated'}, metadata: null, - typeCondition: fragmentType, selections: [idField], + typeCondition: fragmentType, }; } diff --git a/packages/relay-compiler/transforms/RelayGenerateTypeNameTransform.js b/packages/relay-compiler/transforms/RelayGenerateTypeNameTransform.js index 80e01dfc5ec73..ad2086b114d2c 100644 --- a/packages/relay-compiler/transforms/RelayGenerateTypeNameTransform.js +++ b/packages/relay-compiler/transforms/RelayGenerateTypeNameTransform.js @@ -11,18 +11,14 @@ 'use strict'; const IRTransformer = require('../core/GraphQLIRTransformer'); -const SchemaUtils = require('../core/GraphQLSchemaUtils'); const {hasUnaliasedSelection} = require('./RelayTransformUtils'); -const {assertLeafType} = require('graphql'); import type CompilerContext from '../core/GraphQLCompilerContext'; import type {LinkedField, ScalarField} from '../core/GraphQLIR'; - -const {isAbstractType} = SchemaUtils; +import type {Schema} from '../core/Schema'; const TYPENAME_KEY = '__typename'; -const STRING_TYPE = 'String'; type State = { typenameField: ScalarField, @@ -38,7 +34,6 @@ function relayGenerateTypeNameTransform( context: CompilerContext, ): CompilerContext { cache = new Map(); - const stringType = assertLeafType(context.serverSchema.getType(STRING_TYPE)); const typenameField: ScalarField = { kind: 'ScalarField', alias: TYPENAME_KEY, @@ -48,7 +43,7 @@ function relayGenerateTypeNameTransform( loc: {kind: 'Generated'}, metadata: null, name: TYPENAME_KEY, - type: stringType, + type: context.getSchema().expectStringType(), }; const state = { typenameField, @@ -63,13 +58,14 @@ function relayGenerateTypeNameTransform( } function visitLinkedField(field: LinkedField, state: State): LinkedField { + const schema: Schema = this.getContext().getSchema(); let transformedNode = cache.get(field); if (transformedNode != null) { return transformedNode; } - transformedNode = this.traverse(field, state); + transformedNode = (this.traverse(field, state): LinkedField); if ( - isAbstractType(transformedNode.type) && + schema.isAbstractType(schema.getRawType(transformedNode.type)) && !hasUnaliasedSelection(transformedNode, TYPENAME_KEY) ) { transformedNode = { diff --git a/packages/relay-compiler/transforms/RelayMaskTransform.js b/packages/relay-compiler/transforms/RelayMaskTransform.js index 524ec9b1a8de1..391ee4f82447f 100644 --- a/packages/relay-compiler/transforms/RelayMaskTransform.js +++ b/packages/relay-compiler/transforms/RelayMaskTransform.js @@ -53,9 +53,8 @@ function visitFragment(fragment: Fragment, state: State): Fragment { if (state.reachableArguments.length === 0) { return result; } - const schema = this.getContext().serverSchema; const joinedArgumentDefinitions = joinArgumentDefinitions( - schema, + this.getContext().getSchema(), fragment, state.reachableArguments, '@relay(unmask: true)', @@ -80,7 +79,7 @@ function visitFragmentSpread( fragmentSpread.name, ); const context = this.getContext(); - const fragment = context.getFragment(fragmentSpread.name); + const fragment: Fragment = context.getFragment(fragmentSpread.name); const result: InlineFragment = { kind: 'InlineFragment', directives: fragmentSpread.directives, diff --git a/packages/relay-compiler/transforms/RelayMatchTransform.js b/packages/relay-compiler/transforms/RelayMatchTransform.js index 7bcd3c6470563..1cbf9b406c365 100644 --- a/packages/relay-compiler/transforms/RelayMatchTransform.js +++ b/packages/relay-compiler/transforms/RelayMatchTransform.js @@ -15,19 +15,7 @@ const IRTransformer = require('../core/GraphQLIRTransformer'); const getLiteralArgumentValues = require('../core/getLiteralArgumentValues'); const getNormalizationOperationName = require('../core/getNormalizationOperationName'); -const {getRawType} = require('../core/GraphQLSchemaUtils'); const {createUserError} = require('../core/RelayCompilerError'); -const { - assertObjectType, - isObjectType, - GraphQLObjectType, - GraphQLScalarType, - GraphQLInterfaceType, - GraphQLUnionType, - GraphQLList, - GraphQLString, - getNullableType, -} = require('graphql'); const {getModuleComponentKey, getModuleOperationKey} = require('relay-runtime'); import type CompilerContext from '../core/GraphQLCompilerContext'; @@ -37,7 +25,7 @@ import type { LinkedField, ScalarField, } from '../core/GraphQLIR'; -import type {GraphQLCompositeType, GraphQLType} from 'graphql'; +import type {TypeID} from '../core/Schema'; const SUPPORTED_ARGUMENT_NAME = 'supported'; @@ -57,7 +45,7 @@ const SCHEMA_EXTENSION = ` type State = {| +documentName: string, +path: Array, - +parentType: GraphQLType, + +parentType: TypeID, |}; /** @@ -89,14 +77,21 @@ function visitInlineFragment( } function visitScalarField(field: ScalarField): ScalarField { + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); + if (field.name === JS_FIELD_NAME) { - const context: CompilerContext = this.getContext(); - const schema = context.serverSchema; - const jsModuleType = schema.getType(JS_FIELD_TYPE); + const jsModuleType = schema.getTypeFromString(JS_FIELD_TYPE); + if (jsModuleType == null || !schema.isServerType(jsModuleType)) { + throw new createUserError( + `'${JS_FIELD_NAME}' should be defined on the server schema.`, + [field.loc], + ); + } + if ( - jsModuleType != null && - jsModuleType instanceof GraphQLScalarType && - getRawType(field.type).name === jsModuleType.name + schema.isScalar(jsModuleType) && + schema.areEqualTypes(schema.getRawType(field.type), jsModuleType) ) { throw new createUserError( `Direct use of the '${JS_FIELD_NAME}' field is not allowed, use ` + @@ -109,6 +104,9 @@ function visitScalarField(field: ScalarField): ScalarField { } function visitLinkedField(node: LinkedField, state: State): LinkedField { + const context: CompilerContext = this.getContext(); + const schema = context.getSchema(); + state.path.push(node.alias); const transformedNode: LinkedField = this.traverse(node, { ...state, @@ -124,41 +122,39 @@ function visitLinkedField(node: LinkedField, state: State): LinkedField { } const {parentType} = state; - const rawType = getRawType(parentType); - if ( - !( - rawType instanceof GraphQLInterfaceType || - rawType instanceof GraphQLObjectType - ) - ) { + const rawType = schema.getRawType(parentType); + if (!(schema.isInterface(rawType) || schema.isObject(rawType))) { throw createUserError( `@match used on incompatible field '${transformedNode.name}'.` + '@match may only be used with fields whose parent type is an ' + - `interface or object, got invalid type '${String(parentType)}'.`, + `interface or object, got invalid type '${schema.getTypeString( + parentType, + )}'.`, [node.loc], ); } - const context: CompilerContext = this.getContext(); + const currentField = schema.getFieldConfig( + schema.expectField(rawType, transformedNode.name), + ); - const currentField = rawType.getFields()[transformedNode.name]; const supportedArgumentDefinition = currentField.args.find( ({name}) => name === SUPPORTED_ARGUMENT_NAME, ); const supportedArgType = supportedArgumentDefinition != null - ? getNullableType(supportedArgumentDefinition.type) + ? schema.getNullableType(supportedArgumentDefinition.type) : null; const supportedArgOfType = - supportedArgType != null && supportedArgType instanceof GraphQLList - ? supportedArgType.ofType + supportedArgType != null && schema.isList(supportedArgType) + ? schema.getNonListType(supportedArgType) : null; if ( supportedArgumentDefinition == null || supportedArgType == null || supportedArgOfType == null || - getNullableType(supportedArgOfType) !== GraphQLString + !schema.isString(schema.getNullableType(supportedArgOfType)) ) { throw createUserError( `@match used on incompatible field '${transformedNode.name}'. ` + @@ -168,11 +164,9 @@ function visitLinkedField(node: LinkedField, state: State): LinkedField { ); } - const rawFieldType = getRawType(transformedNode.type); - if ( - !(rawFieldType instanceof GraphQLUnionType) && - !(rawFieldType instanceof GraphQLInterfaceType) - ) { + const rawFieldType = schema.getRawType(transformedNode.type); + + if (!schema.isAbstractType(rawFieldType)) { throw createUserError( `@match used on incompatible field '${transformedNode.name}'.` + '@match may only be used with fields that return a union or interface.', @@ -180,7 +174,7 @@ function visitLinkedField(node: LinkedField, state: State): LinkedField { ); } - const seenTypes: Map = new Map(); + const seenTypes: Map = new Map(); const selections = []; transformedNode.selections.forEach(matchSelection => { if ( @@ -210,32 +204,37 @@ function visitLinkedField(node: LinkedField, state: State): LinkedField { if (previousTypeUsage) { throw createUserError( 'Invalid @match selection: each concrete variant/implementor of ' + - `'${String(rawFieldType)}' may be matched against at-most once, ` + - `but '${String(matchedType)}' was matched against multiple times.`, + `'${schema.getTypeString( + rawFieldType, + )}' may be matched against at-most once, ` + + `but '${schema.getTypeString( + matchedType, + )}' was matched against multiple times.`, [matchSelection.loc, previousTypeUsage.loc], ); } seenTypes.set(matchedType, matchSelection); - const possibleConcreteTypes = - rawFieldType instanceof GraphQLUnionType - ? rawFieldType.getTypes() - : context.serverSchema.getPossibleTypes(rawFieldType); - const isPossibleConcreteType = possibleConcreteTypes.some( - type => type.name === matchedType.name, - ); + const possibleConcreteTypes = schema.getPossibleTypes(rawFieldType); + + const isPossibleConcreteType = possibleConcreteTypes.some(type => { + return schema.areEqualTypes(type, matchedType); + }); + if (!isPossibleConcreteType) { let suggestedTypesMessage = 'but no concrete types are defined.'; if (possibleConcreteTypes.length !== 0) { suggestedTypesMessage = `expected one of ${possibleConcreteTypes .slice(0, 3) - .map(type => `'${String(type)}'`) + .map(type => `'${schema.getTypeString(type)}'`) .join(', ')}, etc.`; } throw createUserError( 'Invalid @match selection: selections must match against concrete ' + 'variants/implementors of type ' + - `'${String(transformedNode.type)}'. Got '${String(matchedType)}', ` + + `'${schema.getTypeString( + transformedNode.type, + )}'. Got '${schema.getTypeString(matchedType)}', ` + suggestedTypesMessage, [matchSelection.loc, context.getFragment(moduleImport.name).loc], ); @@ -274,7 +273,9 @@ function visitLinkedField(node: LinkedField, state: State): LinkedField { value: { kind: 'Literal', loc: node.loc, - value: Array.from(seenTypes.keys()).map(type => type.name), + value: Array.from(seenTypes.keys()).map(type => + schema.getTypeString(type), + ), }, loc: node.loc, }, @@ -311,9 +312,17 @@ function visitFragmentSpread( } const context: CompilerContext = this.getContext(); - const schema = context.serverSchema; - const jsModuleType = schema.getType(JS_FIELD_TYPE); - if (jsModuleType == null || !(jsModuleType instanceof GraphQLScalarType)) { + const schema = context.getSchema(); + + const jsModuleType = schema.getTypeFromString(JS_FIELD_TYPE); + if (jsModuleType == null || !schema.isServerType(jsModuleType)) { + throw new createUserError( + `'${JS_FIELD_NAME}' should be defined on the server schema.`, + [spread.loc], + ); + } + + if (!schema.isScalar(jsModuleType)) { throw createUserError( 'Using @module requires the schema to define a scalar ' + `'${JS_FIELD_TYPE}' type.`, @@ -321,16 +330,32 @@ function visitFragmentSpread( } const fragment = context.getFragment(spread.name, spread.loc); - if (!isObjectType(fragment.type)) { + if (!schema.isObject(fragment.type)) { throw createUserError( `@module used on invalid fragment spread '...${spread.name}'. @module ` + 'may only be used with fragments on a concrete (object) type, ' + - `but the fragment has abstract type '${String(fragment.type)}'.`, + `but the fragment has abstract type '${schema.getTypeString( + fragment.type, + )}'.`, [spread.loc, fragment.loc], ); } - const type = assertObjectType(fragment.type); - const jsField = type.getFields()[JS_FIELD_NAME]; + const field = schema.getFieldByName(fragment.type, JS_FIELD_NAME); + if (!field) { + throw createUserError( + `@module used on invalid fragment spread '...${spread.name}'. @module ` + + `requires the fragment type '${schema.getTypeString( + fragment.type, + )}' to have a ` + + `'${JS_FIELD_NAME}(${JS_FIELD_MODULE_ARG}: String! ` + + `[${JS_FIELD_ID_ARG}: String]): ${JS_FIELD_TYPE}' field (your ` + + "schema may choose to omit the 'id' argument but if present it " + + "must accept a 'String').", + [moduleDirective.loc], + ); + } + const jsField = schema.getFieldConfig(field); + const jsFieldModuleArg = jsField ? jsField.args.find(arg => arg.name === JS_FIELD_MODULE_ARG) : null; @@ -338,16 +363,16 @@ function visitFragmentSpread( ? jsField.args.find(arg => arg.name === JS_FIELD_ID_ARG) : null; if ( - jsField == null || jsFieldModuleArg == null || - getNullableType(jsFieldModuleArg.type) !== GraphQLString || - (jsFieldIdArg != null && - getNullableType(jsFieldIdArg.type) !== GraphQLString) || - jsField.type.name !== jsModuleType.name // object identity fails in tests + !schema.isString(schema.getNullableType(jsFieldModuleArg.type)) || + ((jsFieldIdArg != null && !schema.isString(jsFieldIdArg.type)) || + jsField.type !== jsModuleType) ) { throw createUserError( `@module used on invalid fragment spread '...${spread.name}'. @module ` + - `requires the fragment type '${String(fragment.type)}' to have a ` + + `requires the fragment type '${schema.getTypeString( + fragment.type, + )}' to have a ` + `'${JS_FIELD_NAME}(${JS_FIELD_MODULE_ARG}: String! ` + `[${JS_FIELD_ID_ARG}: String]): ${JS_FIELD_TYPE}' field (your ` + "schema may choose to omit the 'id' argument but if present it " + diff --git a/packages/relay-compiler/transforms/RelayRefetchableFragmentTransform.js b/packages/relay-compiler/transforms/RelayRefetchableFragmentTransform.js index a8ecacb9e73b4..45bb1c48ffdd2 100644 --- a/packages/relay-compiler/transforms/RelayRefetchableFragmentTransform.js +++ b/packages/relay-compiler/transforms/RelayRefetchableFragmentTransform.js @@ -11,39 +11,18 @@ 'use strict'; const GraphQLIRVisitor = require('../core/GraphQLIRVisitor'); -const GraphQLSchemaUtils = require('../core/GraphQLSchemaUtils'); const getLiteralArgumentValues = require('../core/getLiteralArgumentValues'); const inferRootArgumentDefinitions = require('../core/inferRootArgumentDefinitions'); -const isEquivalentType = require('../core/isEquivalentType'); -const nullthrows = require('nullthrows'); const { createCombinedError, - createCompilerError, createUserError, eachWithErrors, } = require('../core/RelayCompilerError'); -const { - assertAbstractType, - assertCompositeType, - getNullableType, - GraphQLID, - GraphQLInterfaceType, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLSchema, -} = require('graphql'); +const {generateIDField} = require('../core/SchemaUtils'); import type GraphQLCompilerContext from '../core/GraphQLCompilerContext'; -import type {GraphQLCompositeType} from 'graphql'; -const { - isAbstractType, - implementsInterface, - generateIDField, -} = GraphQLSchemaUtils; - import type { Argument, ArgumentDefinition, @@ -53,6 +32,7 @@ import type { LocalArgumentDefinition, Root, } from '../core/GraphQLIR'; +import type {Schema} from '../core/Schema'; import type {ReaderPaginationMetadata} from 'relay-runtime'; type RefetchRoot = {| @@ -91,11 +71,10 @@ const SCHEMA_EXTENSION = ` function relayRefetchableFragmentTransform( context: GraphQLCompilerContext, ): GraphQLCompilerContext { - const schema = context.serverSchema; - const queryType = schema.getQueryType(); - if (queryType == null) { - throw createUserError('Expected the schema to define a query type.'); - } + const schema = context.getSchema(); + // This will throw (if Query is not available in the schema) + const queryType = schema.expectQueryType(); + const refetchOperations = buildRefetchMap(context); let nextContext = context; const errors = eachWithErrors( @@ -106,34 +85,47 @@ function relayRefetchableFragmentTransform( // functions provide detailed validation as well as case-specific // error messages. let refetchDescriptor; - if (isEquivalentType(fragment.type, queryType)) { + if (schema.areEqualTypes(fragment.type, queryType)) { refetchDescriptor = buildRefetchOperationOnQueryType( + context, schema, fragment, refetchName, ); - } else if (String(fragment.type) === VIEWER_TYPE_NAME) { + } else if (schema.getTypeString(fragment.type) === VIEWER_TYPE_NAME) { // Validate that the schema conforms to the informal Viewer spec // and build the refetch query accordingly. refetchDescriptor = buildRefetchOperationOnViewerType( + context, schema, fragment, refetchName, ); } else if ( - String(fragment.type) === NODE_TYPE_NAME || - (fragment.type instanceof GraphQLObjectType && - fragment.type - .getInterfaces() - .some(interfaceType => String(interfaceType) === NODE_TYPE_NAME)) || - (isAbstractType(fragment.type) && - getImplementations(fragment.type, schema).every(possibleType => - implementsInterface(possibleType, NODE_TYPE_NAME), - )) + schema.getTypeString(fragment.type) === NODE_TYPE_NAME || + (schema.isObject(fragment.type) && + schema + .getInterfaces(fragment.type) + .some(interfaceType => + schema.areEqualTypes( + interfaceType, + schema.expectTypeFromString(NODE_TYPE_NAME), + ), + )) || + (schema.isAbstractType(fragment.type) && + schema + .getPossibleTypes(fragment.type) + .every(possibleType => + schema.implementsInterface( + possibleType, + schema.expectTypeFromString(NODE_TYPE_NAME), + ), + )) ) { // Validate that the schema conforms to the Object Identity (Node) spec // and build the refetch query accordingly. refetchDescriptor = buildRefetchOperationOnNodeType( + context, schema, fragment, refetchName, @@ -149,6 +141,7 @@ function relayRefetchableFragmentTransform( if (refetchDescriptor != null) { const {path, node, transformedFragment} = refetchDescriptor; const connectionMetadata = extractConnectionMetadata( + context.getSchema(), transformedFragment, ); nextContext = nextContext.replace({ @@ -179,14 +172,6 @@ function relayRefetchableFragmentTransform( return nextContext; } -function getImplementations( - type: GraphQLCompositeType, - schema: GraphQLSchema, -): $ReadOnlyArray { - const abstractType = assertAbstractType(assertCompositeType(type)); - return schema.getPossibleTypes(abstractType); -} - /** * Walk the documents of a compiler context and create a mapping of * refetch operation names to the source fragment from which the refetch @@ -237,6 +222,7 @@ function buildRefetchMap( * if there is no connection. */ function extractConnectionMetadata( + schema: Schema, fragment: Fragment, ): ReaderPaginationMetadata | void { const fields = []; @@ -247,8 +233,8 @@ function extractConnectionMetadata( enter(field) { fields.push(field); // Disallow connections within plurals - const pluralOnPath = fields.find( - pathField => getNullableType(pathField.type) instanceof GraphQLList, + const pluralOnPath = fields.find(pathField => + schema.isList(schema.getNullableType(pathField.type)), ); if (pluralOnPath) { throw createUserError( @@ -283,8 +269,8 @@ function extractConnectionMetadata( ); } // Disallow connections within plurals - const pluralOnPath = fields.find( - pathField => getNullableType(pathField.type) instanceof GraphQLList, + const pluralOnPath = fields.find(pathField => + schema.isList(schema.getNullableType(pathField.type)), ); if (pluralOnPath) { throw createUserError( @@ -413,11 +399,12 @@ function buildFragmentSpread(fragment: Fragment): FragmentSpread { } function buildRefetchOperationOnQueryType( - schema: GraphQLSchema, + context: GraphQLCompilerContext, + schema: Schema, fragment: Fragment, queryName: string, ): RefetchRoot { - const queryType = nullthrows(schema.getQueryType()); + const queryType = schema.expectQueryType(); return { path: [], node: { @@ -438,22 +425,24 @@ function buildRefetchOperationOnQueryType( } function buildRefetchOperationOnViewerType( - schema: GraphQLSchema, + context: GraphQLCompilerContext, + schema: Schema, fragment: Fragment, queryName: string, ): RefetchRoot { // Handle fragments on viewer - const queryType = nullthrows(schema.getQueryType()); - const viewerType = schema.getType(VIEWER_TYPE_NAME); - const viewerField = queryType.getFields()[VIEWER_FIELD_NAME]; + const queryType = schema.expectQueryType(); + const viewerType = schema.expectTypeFromString(VIEWER_TYPE_NAME); + const viewerField = schema.getFieldConfig( + schema.expectField(queryType, VIEWER_FIELD_NAME), + ); if ( !( - viewerType instanceof GraphQLObjectType && - viewerField != null && - viewerField.type instanceof GraphQLObjectType && - isEquivalentType(viewerField.type, viewerType) && + schema.isObject(viewerType) && + schema.isObject(viewerField.type) && + schema.areEqualTypes(viewerField.type, viewerType) && viewerField.args.length === 0 && - isEquivalentType(fragment.type, viewerType) + schema.areEqualTypes(fragment.type, viewerType) ) ) { throw createUserError( @@ -497,32 +486,43 @@ function buildRefetchOperationOnViewerType( } function buildRefetchOperationOnNodeType( - schema: GraphQLSchema, + context: GraphQLCompilerContext, + schema: Schema, fragment: Fragment, queryName: string, ): RefetchRoot { - const queryType = nullthrows(schema.getQueryType()); - const nodeType = schema.getType(NODE_TYPE_NAME); - const nodeField = queryType.getFields()[NODE_FIELD_NAME]; + const queryType = schema.expectQueryType(); + const nodeType = schema.expectTypeFromString(NODE_TYPE_NAME); + const nodeField = schema.getFieldConfig( + schema.expectField(queryType, NODE_FIELD_NAME), + ); if ( !( - nodeType instanceof GraphQLInterfaceType && - nodeField != null && - nodeField.type instanceof GraphQLInterfaceType && - isEquivalentType(nodeField.type, nodeType) && + schema.isInterface(nodeType) && + schema.isInterface(nodeField.type) && + schema.areEqualTypes(nodeField.type, nodeType) && nodeField.args.length === 1 && - isEquivalentType(getNullableType(nodeField.args[0].type), GraphQLID) && + schema.areEqualTypes( + schema.getNullableType(nodeField.args[0].type), + schema.expectIdType(), + ) && // the fragment must be on Node or on a type that implements Node - ((fragment.type instanceof GraphQLObjectType && - fragment.type - .getInterfaces() - .some(interfaceType => isEquivalentType(interfaceType, nodeType))) || - (isAbstractType(fragment.type) && - getImplementations(fragment.type, schema).every(possibleType => - possibleType - .getInterfaces() - .some(interfaceType => isEquivalentType(interfaceType, nodeType)), - ))) + ((schema.isObject(fragment.type) && + schema + .getInterfaces(fragment.type) + .some(interfaceType => + schema.areEqualTypes(interfaceType, nodeType), + )) || + (schema.isAbstractType(fragment.type) && + schema + .getPossibleTypes(fragment.type) + .every(possibleType => + schema + .getInterfaces(possibleType) + .some(interfaceType => + schema.areEqualTypes(interfaceType, nodeType), + ), + ))) ) ) { throw createUserError( @@ -538,7 +538,7 @@ function buildRefetchOperationOnNodeType( const idArgName = nodeField.args[0].name; const idArgType = nodeField.args[0].type; // name and type of the query variable - const idVariableType = new GraphQLNonNull(GraphQLID); + const idVariableType = schema.getNonNullType(schema.expectIdType()); const idVariableName = 'id'; const argumentDefinitions = buildOperationArgumentDefinitions( @@ -604,24 +604,30 @@ function buildRefetchOperationOnNodeType( ], type: queryType, }, - transformedFragment: enforceIDField(fragment), + transformedFragment: enforceIDField(context.getSchema(), fragment), }; } -function enforceIDField(fragment: Fragment): Fragment { +function enforceIDField(schema: Schema, fragment: Fragment): Fragment { const idSelection = fragment.selections.find( selection => selection.kind === 'ScalarField' && selection.name === 'id' && selection.alias === 'id' && - isEquivalentType(getNullableType(selection.type), GraphQLID), + schema.areEqualTypes( + schema.getNullableType(selection.type), + schema.expectIdType(), + ), ); if (idSelection) { return fragment; } return { ...fragment, - selections: [...fragment.selections, generateIDField(GraphQLID)], + selections: [ + ...fragment.selections, + generateIDField(schema.expectIdType()), + ], }; } diff --git a/packages/relay-compiler/transforms/RelaySplitModuleImportTransform.js b/packages/relay-compiler/transforms/RelaySplitModuleImportTransform.js index f9c94df2f5d1c..c4e1a5e5d04b4 100644 --- a/packages/relay-compiler/transforms/RelaySplitModuleImportTransform.js +++ b/packages/relay-compiler/transforms/RelaySplitModuleImportTransform.js @@ -21,10 +21,10 @@ import type { ModuleImport, SplitOperation, } from '../core/GraphQLIR'; -import type {GraphQLCompositeType} from 'graphql'; +import type {TypeID} from '../core/Schema'; type State = {| - parentType: GraphQLCompositeType, + parentType: TypeID, splitOperations: Map, |}; @@ -40,7 +40,10 @@ function relaySplitMatchTransform(context: CompilerContext): CompilerContext { InlineFragment: visitInlineFragment, ModuleImport: visitModuleImport, }, - node => ({parentType: node.type, splitOperations}), + node => ({ + parentType: node.type, + splitOperations, + }), ); return transformedContext.addAll(Array.from(splitOperations.values())); } diff --git a/packages/relay-compiler/transforms/RelayTestOperationTransform.js b/packages/relay-compiler/transforms/RelayTestOperationTransform.js index 2eb27f84bb58a..e750a057dc50c 100644 --- a/packages/relay-compiler/transforms/RelayTestOperationTransform.js +++ b/packages/relay-compiler/transforms/RelayTestOperationTransform.js @@ -15,16 +15,9 @@ const IRTransformer = require('../core/GraphQLIRTransformer'); -const { - getNullableType, - isEnumType, - isNullableType, - isListType, -} = require('graphql'); - import type CompilerContext from '../core/GraphQLCompilerContext'; import type {Fragment, Root} from '../core/GraphQLIR'; -import type {GraphQLOutputType, GraphQLList} from 'graphql'; +import type {Schema, TypeID} from '../core/Schema'; // The purpose of this directive is to add GraphQL type inform for fields in // the operation selection in order to use in in RelayMockPayloadGenerator @@ -47,27 +40,22 @@ type TypeDetails = {| +enumValues: null | $ReadOnlyArray, |}; -function getTypeDetails( - fieldType: GraphQLOutputType | GraphQLList, -): TypeDetails { - const nullableType = getNullableType(fieldType); - const isNullable = isNullableType(fieldType); - const isPlural = isListType(nullableType); - const type = isListType(nullableType) - ? getNullableType(nullableType.ofType) - : nullableType; +function getTypeDetails(schema: Schema, fieldType: TypeID): TypeDetails { + const nullableType = schema.getNullableType(fieldType); + const isNullable = !schema.isNonNull(fieldType); + const isPlural = schema.isList(nullableType); + const type = schema.getRawType(nullableType); return { - type: isListType(type) ? String(type) : type != null ? type.name : 'String', - enumValues: isEnumType(type) - ? type.getValues().map(val => val.value) - : null, + type: schema.getTypeString(type), + enumValues: schema.isEnum(type) ? schema.getEnumValues(type) : null, plural: isPlural, nullable: isNullable, }; } function visitRoot(node: Root) { + const schema: Schema = this.getContext().getSchema(); const testDirective = node.directives.find( directive => directive.name === 'relay_test_operation', ); @@ -99,14 +87,14 @@ function visitRoot(node: Root) { case 'ScalarField': { const nextPath = path === null ? selection.alias : `${path}.${selection.alias}`; - selectionsTypeInfo[nextPath] = getTypeDetails(selection.type); + selectionsTypeInfo[nextPath] = getTypeDetails(schema, selection.type); break; } case 'ConnectionField': case 'LinkedField': { const nextPath = path === null ? selection.alias : `${path}.${selection.alias}`; - selectionsTypeInfo[nextPath] = getTypeDetails(selection.type); + selectionsTypeInfo[nextPath] = getTypeDetails(schema, selection.type); queue.unshift({ selections: selection.selections, path: nextPath, diff --git a/packages/relay-compiler/transforms/SkipClientExtensionsTransform.js b/packages/relay-compiler/transforms/SkipClientExtensionsTransform.js index 2f6b74303fce8..deac958ad1f5f 100644 --- a/packages/relay-compiler/transforms/SkipClientExtensionsTransform.js +++ b/packages/relay-compiler/transforms/SkipClientExtensionsTransform.js @@ -25,8 +25,8 @@ function skipClientExtensionTransform( } function visitFragment(node: Fragment): ?Fragment { - const {serverSchema} = this.getContext(); - if (serverSchema.getType(node.type.name)) { + const context: GraphQLCompilerContext = this.getContext(); + if (context.getSchema().isServerType(node.type)) { return this.traverse(node); } return null; diff --git a/packages/relay-compiler/transforms/SkipRedundantNodesTransform.js b/packages/relay-compiler/transforms/SkipRedundantNodesTransform.js index 3f4f521a159f1..f6a2890e75a2a 100644 --- a/packages/relay-compiler/transforms/SkipRedundantNodesTransform.js +++ b/packages/relay-compiler/transforms/SkipRedundantNodesTransform.js @@ -18,6 +18,8 @@ const partitionArray = require('../util/partitionArray'); const getIdentifierForSelection = require('../core/getIdentifierForSelection'); const invariant = require('invariant'); +import type {Schema} from '../core/Schema'; + import type {Fragment, Node, Root, Selection} from '../core/GraphQLIR'; /** @@ -131,7 +133,8 @@ function skipRedundantNodesTransform( let cache = new Map(); function visitNode(node: T): ?T { cache = new Map(); - return transformNode(node, new IMap()).node; + const context: GraphQLCompilerContext = this.getContext(); + return transformNode(context.getSchema(), node, new IMap()).node; } /** @@ -148,6 +151,7 @@ function visitNode(node: T): ?T { * prior to the clone. */ function transformNode( + schema: Schema, node: T, selectionMap: SelectionMap, ): {selectionMap: SelectionMap, node: ?T} { @@ -164,7 +168,7 @@ function transformNode( } const selections = []; sortSelections(node.selections).forEach(selection => { - const identifier = getIdentifierForSelection(selection); + const identifier = getIdentifierForSelection(schema, selection); switch (selection.kind) { case 'ScalarField': case 'FragmentSpread': { @@ -183,6 +187,7 @@ function transformNode( case 'ConnectionField': case 'LinkedField': { const transformed = transformNode( + schema, selection, selectionMap.get(identifier) || new IMap(), ); @@ -197,6 +202,7 @@ function transformNode( // Fork the selection map to prevent conditional selections from // affecting the outer "guaranteed" selections. const transformed = transformNode( + schema, selection, selectionMap.get(identifier) || selectionMap, ); diff --git a/packages/relay-compiler/transforms/ValidateGlobalVariablesTransform.js b/packages/relay-compiler/transforms/ValidateGlobalVariablesTransform.js index b442cf6bf1ddd..88dce74552e25 100644 --- a/packages/relay-compiler/transforms/ValidateGlobalVariablesTransform.js +++ b/packages/relay-compiler/transforms/ValidateGlobalVariablesTransform.js @@ -51,7 +51,12 @@ function validateGlobalVariablesTransform( `Operation '${ node.name }' references undefined variable(s):\n${undefinedVariables - .map(argDef => `- \$${argDef.name}: ${String(argDef.type)}`) + .map( + argDef => + `- \$${argDef.name}: ${context + .getSchema() + .getTypeString(argDef.type)}`, + ) .join('\n')}.`, undefinedVariables.map(argDef => argDef.loc), ); diff --git a/packages/relay-compiler/transforms/__tests__/ClientExtensionsTransform-test.js b/packages/relay-compiler/transforms/__tests__/ClientExtensionsTransform-test.js index fef0afde489d9..08c19e21e5c15 100644 --- a/packages/relay-compiler/transforms/__tests__/ClientExtensionsTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/ClientExtensionsTransform-test.js @@ -14,6 +14,7 @@ const ClientExtensionsTransform = require('../ClientExtensionsTransform'); const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -25,12 +26,19 @@ describe('ClientExtensionsTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/client-extensions-transform`, text => { - const {definitions, schema} = parseGraphQLText(TestSchema, text); - return new GraphQLCompilerContext(TestSchema, schema ?? TestSchema) + const {definitions, schema: extendedSchema} = parseGraphQLText( + TestSchema, + text, + ); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ClientExtensionsTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/FilterDirectivesTransform-test.js b/packages/relay-compiler/transforms/__tests__/FilterDirectivesTransform-test.js index 2c5b64d37f4d8..3762fc0e551c4 100644 --- a/packages/relay-compiler/transforms/__tests__/FilterDirectivesTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/FilterDirectivesTransform-test.js @@ -14,6 +14,7 @@ const FilterDirectivesTransform = require('../FilterDirectivesTransform'); const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -31,11 +32,16 @@ describe('FilterDirectivesTransform', () => { 'directive @exampleFilteredDirective on FIELD', ]); const {definitions} = parseGraphQLText(extendedSchema, text); - return new GraphQLCompilerContext(TestSchema, extendedSchema) + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([FilterDirectivesTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/FlattenTransform-test.js b/packages/relay-compiler/transforms/__tests__/FlattenTransform-test.js index 007afa589f0c5..6ea7b9621d055 100644 --- a/packages/relay-compiler/transforms/__tests__/FlattenTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/FlattenTransform-test.js @@ -17,6 +17,7 @@ const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayMatchTransform = require('../../transforms/RelayMatchTransform'); const RelayParser = require('../../core/RelayParser'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -35,14 +36,18 @@ describe('FlattenTransform', () => { RelayMatchTransform.SCHEMA_EXTENSION, RelayRelayDirectiveTransform.SCHEMA_EXTENSION, ]); - return new GraphQLCompilerContext(TestSchema, extendedSchema) - .addAll(RelayParser.parse(extendedSchema, text)) + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) + .addAll(RelayParser.parse(compilerSchema, text)) .applyTransforms([ RelayMatchTransform.transform, FlattenTransform.transformWithOptions(options), ]) .documents() - .map(doc => GraphQLIRPrinter.print(doc)) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }; } diff --git a/packages/relay-compiler/transforms/__tests__/InlineDataFragmentTransform-test.js b/packages/relay-compiler/transforms/__tests__/InlineDataFragmentTransform-test.js index ed408b114a0b1..f1d94d36fac3e 100644 --- a/packages/relay-compiler/transforms/__tests__/InlineDataFragmentTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/InlineDataFragmentTransform-test.js @@ -12,6 +12,7 @@ 'use strict'; const InlineDataFragmentTransform = require('../InlineDataFragmentTransform'); +const Schema = require('../../core/Schema'); const { CompilerContext, @@ -24,19 +25,23 @@ const { generateTestsFromFixtures, } = require('relay-test-utils-internal'); -const schema = transformASTSchema(TestSchema, [ +const extendedSchema = transformASTSchema(TestSchema, [ InlineDataFragmentTransform.SCHEMA_EXTENSION, ]); generateTestsFromFixtures( `${__dirname}/fixtures/inline-data-fragment-transform`, text => { - const {definitions} = parseGraphQLText(schema, text); - return new CompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new CompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([InlineDataFragmentTransform.transform]) .documents() - .map(doc => Printer.print(doc)) + .map(doc => Printer.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/InlineFragmentsTransform-test.js b/packages/relay-compiler/transforms/__tests__/InlineFragmentsTransform-test.js index fbc148964a74c..2a0ed1b7b0fa9 100644 --- a/packages/relay-compiler/transforms/__tests__/InlineFragmentsTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/InlineFragmentsTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const InlineFragmentsTransform = require('../InlineFragmentsTransform'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -25,12 +26,13 @@ describe('InlineFragmentsTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/inline-fragments-transform`, text => { - const {schema, definitions} = parseGraphQLText(TestSchema, text); - return new GraphQLCompilerContext(TestSchema, schema ?? TestSchema) + const {definitions} = parseGraphQLText(TestSchema, text); + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([InlineFragmentsTransform.transform]) .documents() - .map(doc => GraphQLIRPrinter.print(doc)) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayApplyFragmentArgumentTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayApplyFragmentArgumentTransform-test.js index ed89d933f04f7..296acc2dc29c2 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayApplyFragmentArgumentTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayApplyFragmentArgumentTransform-test.js @@ -15,6 +15,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayApplyFragmentArgumentTransform = require('../RelayApplyFragmentArgumentTransform'); const RelayParser = require('../../core/RelayParser'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -25,12 +26,13 @@ describe('RelayApplyFragmentArgumentTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/apply-fragment-argument-transform`, text => { - const ast = RelayParser.parse(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + const ast = RelayParser.parse(compilerSchema, text); + return new GraphQLCompilerContext(compilerSchema) .addAll(ast) .applyTransforms([RelayApplyFragmentArgumentTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayDeferStreamTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayDeferStreamTransform-test.js index 03fccc20a2235..fa215ac1699ca 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayDeferStreamTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayDeferStreamTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayDeferStreamTransform = require('../RelayDeferStreamTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -23,24 +24,22 @@ const { } = require('relay-test-utils-internal'); describe('RelayDeferStreamTransform', () => { - const schema = transformASTSchema(TestSchema, []); + const extendedSchema = transformASTSchema(TestSchema, []); describe('when streaming is enabled', () => { generateTestsFromFixtures( `${__dirname}/fixtures/relay-defer-stream-transform`, text => { - const {definitions, schema: clientSchema} = parseGraphQLText( - schema, - text, - ); - return new GraphQLCompilerContext( + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( TestSchema, - clientSchema ?? TestSchema, - ) + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([RelayDeferStreamTransform.transform]) .documents() - .map(doc => GraphQLIRPrinter.print(doc)) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayFieldHandleTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayFieldHandleTransform-test.js index 9b8ea0e1153be..c4c296846b214 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayFieldHandleTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayFieldHandleTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayFieldHandleTransform = require('../RelayFieldHandleTransform'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -26,11 +27,12 @@ describe('RelayFieldHandleTransform', () => { `${__dirname}/fixtures/field-handle-transform`, text => { const {definitions} = parseGraphQLText(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([RelayFieldHandleTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayGenerateIDFieldTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayGenerateIDFieldTransform-test.js index cb7764f7337e2..367cd77e8b2b7 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayGenerateIDFieldTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayGenerateIDFieldTransform-test.js @@ -15,6 +15,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayGenerateIDFieldTransform = require('../RelayGenerateIDFieldTransform'); const RelayParser = require('../../core/RelayParser'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -25,12 +26,13 @@ describe('RelayGenerateIDFieldTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/generate-id-field-transform`, text => { - const ast = RelayParser.parse(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + const ast = RelayParser.parse(compilerSchema, text); + return new GraphQLCompilerContext(compilerSchema) .addAll(ast) .applyTransforms([RelayGenerateIDFieldTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayGenerateTypeNameTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayGenerateTypeNameTransform-test.js index 7067b24409906..33e2595235b68 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayGenerateTypeNameTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayGenerateTypeNameTransform-test.js @@ -16,6 +16,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const InlineFragmentsTransform = require('../InlineFragmentsTransform'); const RelayGenerateTypeNameTransform = require('../RelayGenerateTypeNameTransform'); const RelayParser = require('../../core/RelayParser'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -26,8 +27,9 @@ describe('RelayGenerateTypeNameTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/generate-typename-transform`, text => { - const ast = RelayParser.parse(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + const ast = RelayParser.parse(compilerSchema, text); + return new GraphQLCompilerContext(compilerSchema) .addAll(ast) .applyTransforms([ InlineFragmentsTransform.transform, diff --git a/packages/relay-compiler/transforms/__tests__/RelayMaskTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayMaskTransform-test.js index 963ca3c78dd1f..d67f69940263a 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayMaskTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayMaskTransform-test.js @@ -15,6 +15,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayMaskTransform = require('../RelayMaskTransform'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -24,15 +25,19 @@ const { } = require('relay-test-utils-internal'); describe('RelayMaskTransform', () => { - const schema = transformASTSchema(TestSchema, [ + const extendedSchema = transformASTSchema(TestSchema, [ RelayRelayDirectiveTransform.SCHEMA_EXTENSION, ]); generateTestsFromFixtures( `${__dirname}/fixtures/relay-mask-transform`, text => { - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ // Requires Relay directive transform first. @@ -40,7 +45,7 @@ describe('RelayMaskTransform', () => { RelayMaskTransform.transform, ]) .documents() - .map(doc => GraphQLIRPrinter.print(doc)) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); @@ -48,8 +53,12 @@ describe('RelayMaskTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/relay-mask-transform-variables`, text => { - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ // Requires Relay directive transform first. @@ -58,9 +67,12 @@ describe('RelayMaskTransform', () => { ]) .documents() .map(doc => { - const printed = GraphQLIRPrinter.print(doc); - // $FlowFixMe - argumentDefinitions is missing in SplitOperation - const json = JSON.stringify(doc.argumentDefinitions, null, 2); + const printed = GraphQLIRPrinter.print(compilerSchema, doc); + const argumentDefinitions = + doc.kind === 'Root' || doc.kind === 'Fragment' + ? doc.argumentDefinitions + : null; + const json = JSON.stringify(argumentDefinitions, null, 2); return printed + json; }) .join('\n\n'); diff --git a/packages/relay-compiler/transforms/__tests__/RelayMatchTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayMatchTransform-test.js index f41fdb97ac395..3c974b5f6dede 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayMatchTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayMatchTransform-test.js @@ -15,6 +15,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayMatchTransform = require('../RelayMatchTransform'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -24,17 +25,18 @@ const { } = require('relay-test-utils-internal'); describe('RelayMatchTransform', () => { - const schema = transformASTSchema(TestSchema, [ + const extendedSchema = transformASTSchema(TestSchema, [ RelayMatchTransform.SCHEMA_EXTENSION, ]); generateTestsFromFixtures( `${__dirname}/fixtures/relay-match-transform`, text => { - const {definitions, schema: clientSchema} = parseGraphQLText( - schema, - text, + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, ); - return new GraphQLCompilerContext(TestSchema, clientSchema ?? TestSchema) + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ // Requires Relay directive transform first. @@ -42,7 +44,7 @@ describe('RelayMatchTransform', () => { RelayMatchTransform.transform, ]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayRefetchableFragmentTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayRefetchableFragmentTransform-test.js index 3c9b96fb05ad5..f5f2fa4e18b2c 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayRefetchableFragmentTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayRefetchableFragmentTransform-test.js @@ -16,6 +16,7 @@ const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayConnectionTransform = require('../../handlers/connection/RelayConnectionTransform'); const RelayRefetchableFragmentTransform = require('../RelayRefetchableFragmentTransform'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -25,7 +26,7 @@ const { } = require('relay-test-utils-internal'); describe('RelayRefetchableFragmentTransform', () => { - const schema = transformASTSchema(TestSchema, [ + const extendedSchema = transformASTSchema(TestSchema, [ RelayConnectionTransform.SCHEMA_EXTENSION, RelayRefetchableFragmentTransform.SCHEMA_EXTENSION, ]); @@ -33,8 +34,12 @@ describe('RelayRefetchableFragmentTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/relay-refetchable-fragment-transform`, text => { - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ // Requires Relay directive transform first. @@ -43,7 +48,7 @@ describe('RelayRefetchableFragmentTransform', () => { RelayRefetchableFragmentTransform.transform, ]) .documents() - .map(doc => GraphQLIRPrinter.print(doc)) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayRelayDirectiveTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayRelayDirectiveTransform-test.js index 8afe97eb3d42b..638e313ac8a88 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayRelayDirectiveTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayRelayDirectiveTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const RelayParser = require('../../core/RelayParser'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -28,8 +29,9 @@ describe('RelayRelayDirectiveTransform', () => { const schema = transformASTSchema(TestSchema, [ RelayRelayDirectiveTransform.SCHEMA_EXTENSION, ]); - const ast = RelayParser.parse(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema, schema); + const ast = RelayParser.parse(compilerSchema, text); + return new GraphQLCompilerContext(compilerSchema) .addAll(ast) .applyTransforms([RelayRelayDirectiveTransform.transform]) .documents() diff --git a/packages/relay-compiler/transforms/__tests__/RelaySkipHandleFieldTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelaySkipHandleFieldTransform-test.js index face01451baeb..6d0267d76c383 100644 --- a/packages/relay-compiler/transforms/__tests__/RelaySkipHandleFieldTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelaySkipHandleFieldTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelaySkipHandleFieldTransform = require('../RelaySkipHandleFieldTransform'); +const Schema = require('../../core/Schema'); const { TestSchema, @@ -26,11 +27,12 @@ describe('RelaySkipHandleFieldTransform', () => { `${__dirname}/fixtures/skip-handle-field-transform`, text => { const {definitions} = parseGraphQLText(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([RelaySkipHandleFieldTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelaySplitModuleImportTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelaySplitModuleImportTransform-test.js index 857647666a9ea..be50968a31370 100644 --- a/packages/relay-compiler/transforms/__tests__/RelaySplitModuleImportTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelaySplitModuleImportTransform-test.js @@ -16,6 +16,7 @@ const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayMatchTransform = require('../RelayMatchTransform'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); const RelaySplitModuleImportTransform = require('../RelaySplitModuleImportTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -25,15 +26,16 @@ const { } = require('relay-test-utils-internal'); describe('RelayMatchTransform', () => { - const schema = transformASTSchema(TestSchema, [ + const extendedSchema = transformASTSchema(TestSchema, [ RelayMatchTransform.SCHEMA_EXTENSION, ]); generateTestsFromFixtures( `${__dirname}/fixtures/relay-split-module-import-transform`, text => { - const {definitions} = parseGraphQLText(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ // Requires Relay directive transform first. @@ -42,7 +44,7 @@ describe('RelayMatchTransform', () => { RelaySplitModuleImportTransform.transform, ]) .documents() - .map(doc => GraphQLIRPrinter.print(doc)) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/RelayTestOperationTransform-test.js b/packages/relay-compiler/transforms/__tests__/RelayTestOperationTransform-test.js index 9fdee27d242e8..3f79744e82f60 100644 --- a/packages/relay-compiler/transforms/__tests__/RelayTestOperationTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/RelayTestOperationTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const RelayParser = require('../../core/RelayParser'); const RelayTestOperationTransform = require('../RelayTestOperationTransform'); +const Schema = require('../../core/Schema'); const {transformASTSchema} = require('../../core/ASTConvert'); const { @@ -28,8 +29,9 @@ describe('RelayTestOperationTransform', () => { const schema = transformASTSchema(TestSchema, [ RelayTestOperationTransform.SCHEMA_EXTENSION, ]); - const ast = RelayParser.parse(schema, text); - return new GraphQLCompilerContext(TestSchema, schema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema, schema); + const ast = RelayParser.parse(compilerSchema, text); + return new GraphQLCompilerContext(compilerSchema) .addAll(ast) .applyTransforms([RelayTestOperationTransform.transform]) .documents() diff --git a/packages/relay-compiler/transforms/__tests__/SkipClientExtensionsTransform-test.js b/packages/relay-compiler/transforms/__tests__/SkipClientExtensionsTransform-test.js index 1ba8e21be3145..3b9e5dd6df61b 100644 --- a/packages/relay-compiler/transforms/__tests__/SkipClientExtensionsTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/SkipClientExtensionsTransform-test.js @@ -14,6 +14,7 @@ const ClientExtensionsTransform = require('../ClientExtensionsTransform'); const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); +const Schema = require('../../core/Schema'); const SkipClientExtensionsTransform = require('../SkipClientExtensionsTransform'); const { @@ -26,15 +27,22 @@ describe('SkipClientExtensionsTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/skip-client-extensions-transform`, text => { - const {definitions, schema} = parseGraphQLText(TestSchema, text); - return new GraphQLCompilerContext(TestSchema, schema ?? TestSchema) + const {definitions, schema: extendedSchema} = parseGraphQLText( + TestSchema, + text, + ); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, + ); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ ClientExtensionsTransform.transform, SkipClientExtensionsTransform.transform, ]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/SkipRedundantNodesTransform-test.js b/packages/relay-compiler/transforms/__tests__/SkipRedundantNodesTransform-test.js index 788543fb16ea3..ccc1ac087549c 100644 --- a/packages/relay-compiler/transforms/__tests__/SkipRedundantNodesTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/SkipRedundantNodesTransform-test.js @@ -16,6 +16,7 @@ const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const InlineFragmentsTransform = require('../InlineFragmentsTransform'); const RelayMatchTransform = require('../RelayMatchTransform'); const RelayRelayDirectiveTransform = require('../RelayRelayDirectiveTransform'); +const Schema = require('../../core/Schema'); const SkipRedundantNodesTransform = require('../SkipRedundantNodesTransform'); const {transformASTSchema} = require('../../core/ASTConvert'); @@ -26,17 +27,18 @@ const { } = require('relay-test-utils-internal'); describe('SkipRedundantNodesTransform', () => { - const schema = transformASTSchema(TestSchema, [ + const extendedSchema = transformASTSchema(TestSchema, [ RelayMatchTransform.SCHEMA_EXTENSION, ]); generateTestsFromFixtures( `${__dirname}/fixtures/skip-redundant-nodes-transform`, text => { - const {definitions, schema: clientSchema} = parseGraphQLText( - schema, - text, + const {definitions} = parseGraphQLText(extendedSchema, text); + const compilerSchema = Schema.DEPRECATED__create( + TestSchema, + extendedSchema, ); - return new GraphQLCompilerContext(TestSchema, clientSchema ?? TestSchema) + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([ RelayRelayDirectiveTransform.transform, @@ -45,7 +47,7 @@ describe('SkipRedundantNodesTransform', () => { SkipRedundantNodesTransform.transform, ]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/SkipUnreachableNodeTransform-test.js b/packages/relay-compiler/transforms/__tests__/SkipUnreachableNodeTransform-test.js index a3bfadcd97228..a833cfccaecd1 100644 --- a/packages/relay-compiler/transforms/__tests__/SkipUnreachableNodeTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/SkipUnreachableNodeTransform-test.js @@ -14,6 +14,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); const RelayParser = require('../../core/RelayParser'); +const Schema = require('../../core/Schema'); const SkipUnreachableNodeTransform = require('../SkipUnreachableNodeTransform'); const { @@ -25,12 +26,13 @@ describe('SkipUnreachableNodeTransform', () => { generateTestsFromFixtures( `${__dirname}/fixtures/skip-unreachable-node-transform`, text => { - const ast = RelayParser.parse(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const schema = Schema.DEPRECATED__create(TestSchema); + const ast = RelayParser.parse(schema, text); + return new GraphQLCompilerContext(schema) .addAll(ast) .applyTransforms([SkipUnreachableNodeTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(schema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/transforms/__tests__/SkipUnusedVariablesTransform-test.js b/packages/relay-compiler/transforms/__tests__/SkipUnusedVariablesTransform-test.js index 1d7f9824f5d75..54e4a89be79ba 100644 --- a/packages/relay-compiler/transforms/__tests__/SkipUnusedVariablesTransform-test.js +++ b/packages/relay-compiler/transforms/__tests__/SkipUnusedVariablesTransform-test.js @@ -13,6 +13,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const GraphQLIRPrinter = require('../../core/GraphQLIRPrinter'); +const Schema = require('../../core/Schema'); const SkipUnusedVariablesTransform = require('../SkipUnusedVariablesTransform'); const { @@ -25,11 +26,12 @@ generateTestsFromFixtures( `${__dirname}/fixtures/skip-unused-variables-transform`, text => { const {definitions} = parseGraphQLText(TestSchema, text); - return new GraphQLCompilerContext(TestSchema) + const compilerSchema = Schema.DEPRECATED__create(TestSchema); + return new GraphQLCompilerContext(compilerSchema) .addAll(definitions) .applyTransforms([SkipUnusedVariablesTransform.transform]) .documents() - .map(GraphQLIRPrinter.print) + .map(doc => GraphQLIRPrinter.print(compilerSchema, doc)) .join('\n'); }, ); diff --git a/packages/relay-compiler/util/joinArgumentDefinitions.js b/packages/relay-compiler/util/joinArgumentDefinitions.js index c811b51459701..47b5464ab0241 100644 --- a/packages/relay-compiler/util/joinArgumentDefinitions.js +++ b/packages/relay-compiler/util/joinArgumentDefinitions.js @@ -6,17 +6,16 @@ * * All rights reserved. * - * @flow + * @flow strict-local * @format */ 'use strict'; const {createUserError} = require('../core/RelayCompilerError'); -const {isTypeSubTypeOf} = require('graphql'); import type {ArgumentDefinition, Fragment} from '../core/GraphQLIR'; -import type {GraphQLSchema} from 'graphql'; +import type {Schema} from '../core/Schema'; /** * Attempts to join the argument definitions for a root fragment @@ -25,7 +24,7 @@ import type {GraphQLSchema} from 'graphql'; * variable(s) are used in incompatible ways in different fragments. */ function joinArgumentDefinitions( - schema: GraphQLSchema, + schema: Schema, fragment: Fragment, reachableArguments: $ReadOnlyArray, directiveName: string, @@ -59,7 +58,7 @@ function joinArgumentDefinitions( * null to indicate they cannot be joined. */ function joinArgumentDefinition( - schema: GraphQLSchema, + schema: Schema, prevArgDef: ArgumentDefinition, nextArgDef: ArgumentDefinition, directiveName: string, @@ -80,15 +79,17 @@ function joinArgumentDefinition( `applying ${directiveName}.`, [prevArgDef.loc, nextArgDef.loc], ); - } else if (isTypeSubTypeOf(schema, nextArgDef.type, prevArgDef.type)) { + } else if (schema.isTypeSubTypeOf(nextArgDef.type, prevArgDef.type)) { // prevArgDef is less strict than nextArgDef return nextArgDef; - } else if (isTypeSubTypeOf(schema, prevArgDef.type, nextArgDef.type)) { + } else if (schema.isTypeSubTypeOf(prevArgDef.type, nextArgDef.type)) { return prevArgDef; } else { throw createUserError( 'Cannot combine variables with incompatible types ' + - `${String(prevArgDef.type)} and ${String(nextArgDef.type)} ` + + `${schema.getTypeString(prevArgDef.type)} and ${schema.getTypeString( + nextArgDef.type, + )} ` + `when applying ${directiveName}.`, [prevArgDef.loc, nextArgDef.loc], ); diff --git a/packages/relay-compiler/validations/__tests__/validateRelayRequiredArguments-test.js b/packages/relay-compiler/validations/__tests__/validateRelayRequiredArguments-test.js index 44c2715dcf54a..1b5da7cd0cf5d 100644 --- a/packages/relay-compiler/validations/__tests__/validateRelayRequiredArguments-test.js +++ b/packages/relay-compiler/validations/__tests__/validateRelayRequiredArguments-test.js @@ -13,6 +13,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const RelayIRTransforms = require('../../core/RelayIRTransforms'); +const Schema = require('../../core/Schema'); const validateRelayRequiredArguments = require('../validateRelayRequiredArguments'); @@ -31,10 +32,9 @@ describe('validateRelayRequiredArguments-test', () => { generateTestsFromFixtures( `${__dirname}/fixtures/required-arguments`, text => { - const {definitions, schema} = parseGraphQLText(relaySchema, text); + const {definitions} = parseGraphQLText(relaySchema, text); const codegenContext = new GraphQLCompilerContext( - TestSchema, - schema ?? TestSchema, + Schema.DEPRECATED__create(TestSchema, relaySchema), ) .addAll(definitions) .applyTransforms([ diff --git a/packages/relay-compiler/validations/__tests__/validateRelayServerOnlyDirectives-test.js b/packages/relay-compiler/validations/__tests__/validateRelayServerOnlyDirectives-test.js index aaa96bfc6fe31..a6877b9e0f5fa 100644 --- a/packages/relay-compiler/validations/__tests__/validateRelayServerOnlyDirectives-test.js +++ b/packages/relay-compiler/validations/__tests__/validateRelayServerOnlyDirectives-test.js @@ -13,6 +13,7 @@ const GraphQLCompilerContext = require('../../core/GraphQLCompilerContext'); const RelayIRTransforms = require('../../core/RelayIRTransforms'); +const Schema = require('../../core/Schema'); const validateRelayServerOnlyDirectives = require('../validateRelayServerOnlyDirectives'); @@ -33,8 +34,7 @@ describe('ValidateRelayServerOnlyDirectives', () => { text => { const {definitions, schema} = parseGraphQLText(relaySchema, text); const codegenContext = new GraphQLCompilerContext( - TestSchema, - schema ?? TestSchema, + Schema.DEPRECATED__create(TestSchema, schema), ) .addAll(definitions) .applyTransforms([ diff --git a/packages/relay-compiler/validations/validateRelayRequiredArguments.js b/packages/relay-compiler/validations/validateRelayRequiredArguments.js index f9a96b76c2958..6c2c942616c55 100644 --- a/packages/relay-compiler/validations/validateRelayRequiredArguments.js +++ b/packages/relay-compiler/validations/validateRelayRequiredArguments.js @@ -14,7 +14,6 @@ const GraphQLIRValidator = require('../core/GraphQLIRValidator'); const {createUserError} = require('../core/RelayCompilerError'); const {getFieldDefinitionStrict} = require('../core/getFieldDefinition'); -const {isRequiredArgument} = require('graphql'); import type GraphQLCompilerContext from '../core/GraphQLCompilerContext'; import type { @@ -25,11 +24,11 @@ import type { Root, SplitOperation, } from '../core/GraphQLIR'; -import type {GraphQLOutputType, GraphQLArgument} from 'graphql'; +import type {Schema, TypeID, FieldArgument} from '../core/Schema'; type State = {| +rootNode: Fragment | Root | SplitOperation, - +parentType: GraphQLOutputType, + +parentType: TypeID, |}; /* @@ -50,13 +49,18 @@ function validateRelayRequiredArguments(context: GraphQLCompilerContext): void { ); } -function visitDirective(node: Directive, {parentType, rootNode}: State): void { - const context = this.getContext(); - const directiveDef = context.serverSchema.getDirective(node.name); +function visitDirective(node: Directive, {rootNode}: State): void { + const context: GraphQLCompilerContext = this.getContext(); + const directiveDef = context.getSchema().getDirective(node.name); if (directiveDef == null) { return; } - validateRequiredArguments(node, directiveDef.args, rootNode); + validateRequiredArguments( + context.getSchema(), + node, + directiveDef.args, + rootNode, + ); } function visitInlineFragment(fragment, {rootNode}: State): void { @@ -67,24 +71,28 @@ function visitInlineFragment(fragment, {rootNode}: State): void { } function visitField(node: Field, {parentType, rootNode}: State): void { - const context = this.getContext(); - const definition = getFieldDefinitionStrict( - context.serverSchema, - parentType, - node.name, - ); + const context: GraphQLCompilerContext = this.getContext(); + const schema = context.getSchema(); + const definition = getFieldDefinitionStrict(schema, parentType, node.name); if (definition == null) { const isLegacyFatInterface = node.directives.some( directive => directive.name === 'fixme_fat_interface', ); if (!isLegacyFatInterface) { throw createUserError( - `Unknown field '${node.name}' on type '${String(parentType)}'.`, + `validateRelayRequiredArguments: Unknown field '${ + node.name + }' on type '${schema.getTypeString(parentType)}'.`, [node.loc], ); } } else { - validateRequiredArguments(node, definition.args, rootNode); + validateRequiredArguments( + schema, + node, + schema.getFieldConfig(definition).args, + rootNode, + ); } this.traverse(node, { rootNode, @@ -93,18 +101,20 @@ function visitField(node: Field, {parentType, rootNode}: State): void { } function validateRequiredArguments( + schema: Schema, node: Connection | Directive | Field, - definitionArgs: $ReadOnlyArray, + definitionArgs: $ReadOnlyArray, rootNode, ): void { for (const arg of definitionArgs) { if ( - isRequiredArgument(arg) && + schema.isNonNull(arg.type) && !node.args.some(actualArg => actualArg.name === arg.name) ) { throw createUserError( - `Required argument '${arg.name}: ${String(arg.type)}' is missing ` + - `on '${node.name}' in '${rootNode.name}'.`, + `Required argument '${arg.name}: ${schema.getTypeString( + arg.type, + )}' is missing on '${node.name}' in '${rootNode.name}'.`, [node.loc, rootNode.loc], ); } diff --git a/packages/relay-test-utils-internal/TestCompiler.js b/packages/relay-test-utils-internal/TestCompiler.js index 283a223179375..ea2c2ce0befb3 100644 --- a/packages/relay-test-utils-internal/TestCompiler.js +++ b/packages/relay-test-utils-internal/TestCompiler.js @@ -19,6 +19,7 @@ const { IRTransforms, compileRelayArtifacts, transformASTSchema, + Schema, } = require('relay-compiler'); import type {GraphQLSchema} from 'graphql'; @@ -73,10 +74,13 @@ function generate( moduleMap: ?{[string]: mixed}, ): {[key: string]: GeneratedNode} { const relaySchema = transformASTSchema(schema, IRTransforms.schemaExtensions); - const compilerContext = new GraphQLCompilerContext( - schema, + const {definitions, schema: extendedSchema} = parseGraphQLText( relaySchema, - ).addAll(parseGraphQLText(relaySchema, text).definitions); + text, + ); + const compilerContext = new GraphQLCompilerContext( + Schema.DEPRECATED__create(schema, extendedSchema), + ).addAll(definitions); const documentMap = {}; compileRelayArtifacts(compilerContext, transforms).forEach( ([_definition, node]) => { diff --git a/packages/relay-test-utils-internal/parseGraphQLText.js b/packages/relay-test-utils-internal/parseGraphQLText.js index 2fde5db584f1a..0980e78785673 100644 --- a/packages/relay-test-utils-internal/parseGraphQLText.js +++ b/packages/relay-test-utils-internal/parseGraphQLText.js @@ -11,7 +11,7 @@ 'use strict'; const {extendSchema, parse} = require('graphql'); -const {Parser, convertASTDocuments} = require('relay-compiler'); +const {Parser, Schema, convertASTDocuments} = require('relay-compiler'); import type {GraphQLSchema} from 'graphql'; import type {Fragment, Root} from 'relay-compiler'; @@ -21,20 +21,19 @@ function parseGraphQLText( text: string, ): { definitions: $ReadOnlyArray, - schema: ?GraphQLSchema, + schema: GraphQLSchema, } { const ast = parse(text); - // TODO T24511737 figure out if this is dangerous const extendedSchema = extendSchema(schema, ast, {assumeValid: true}); const definitions = convertASTDocuments( - extendedSchema, + Schema.DEPRECATED__create(schema, extendedSchema), [ast], [], Parser.transform.bind(Parser), ); return { definitions, - schema: extendedSchema !== schema ? extendedSchema : null, + schema: extendedSchema, }; }