From 0a5d36223e3d57a4da4b876dc6be4c5dde7bc6ec Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 23 Aug 2020 17:52:42 +0300 Subject: [PATCH] enable defer stream across schema creation --- package.json | 2 +- .../delegate/src/defaultMergedResolver.ts | 10 ++++ packages/delegate/tests/deferStream.test.ts | 51 +++++++++++++++++++ .../tests/loaders/schema/integration.spec.ts | 25 +++++++++ .../src/buildSchemaFromTypeDefinitions.ts | 12 +++-- packages/schema/src/types.ts | 8 +++ packages/stitch/src/stitchSchemas.ts | 2 + packages/stitch/src/stitchingInfo.ts | 2 + packages/utils/src/addTypes.ts | 2 + packages/utils/src/mapSchema.ts | 2 + yarn.lock | 8 +-- 11 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 packages/delegate/tests/deferStream.test.ts diff --git a/package.json b/package.json index 29ff8c93b71..412c93d9477 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,6 @@ "./website" ], "resolutions": { - "graphql": "15.3.0" + "graphql": "npm:graphql-experimental" } } \ No newline at end of file diff --git a/packages/delegate/src/defaultMergedResolver.ts b/packages/delegate/src/defaultMergedResolver.ts index 083dbe73375..2a739623890 100644 --- a/packages/delegate/src/defaultMergedResolver.ts +++ b/packages/delegate/src/defaultMergedResolver.ts @@ -33,6 +33,16 @@ export function defaultMergedResolver( const data = parent[responseKey]; const unpathedErrors = getUnpathedErrors(parent); + + // To Do: extract this section to separate function. + // If proxied result with defer or stream, result may be initially undefined + // so must call out to a to-be-created Receiver abstraction + // (as opposed to Dispatcher within graphql-js) that will notify of data arrival + + if (data === undefined) { + return 'test'; + } + const subschema = getSubschema(parent, responseKey); return resolveExternalValue(data, unpathedErrors, subschema, context, info); diff --git a/packages/delegate/tests/deferStream.test.ts b/packages/delegate/tests/deferStream.test.ts new file mode 100644 index 00000000000..7830782c9e0 --- /dev/null +++ b/packages/delegate/tests/deferStream.test.ts @@ -0,0 +1,51 @@ +import { graphql } from 'graphql/experimental'; + +import { makeExecutableSchema } from '@graphql-tools/schema'; + +describe('defer support', () => { + test('should work', async () => { + const schema = makeExecutableSchema({ + typeDefs: ` + type Query { + test(input: String): String + } + `, + resolvers: { + Query: { + test: (_root, args) => args.input, + } + }, + }); + + const result = await graphql( + schema, + ` + query { + ... on Query @defer { + test(input: "test") + } + } + `, + ); + + const results = []; + if (result[Symbol.asyncIterator]) { + for await (let patch of result) { + results.push(patch); + } + } + + expect(results[0]).toEqual({ + data: {}, + hasNext: true, + }); + expect(results[1]).toEqual({ + data: { + test: 'test' + }, + hasNext: false, + path: [], + }); + + }); +}); diff --git a/packages/load/tests/loaders/schema/integration.spec.ts b/packages/load/tests/loaders/schema/integration.spec.ts index aedfd07a2c8..d81aca7b96d 100644 --- a/packages/load/tests/loaders/schema/integration.spec.ts +++ b/packages/load/tests/loaders/schema/integration.spec.ts @@ -53,6 +53,31 @@ describe('loadSchema', () => { const schemaStr = printSchema(schema); expect(schemaStr).toBeSimilarGqlDoc(/* GraphQL */` + """ + Directs the executor to defer this fragment when the \`if\` argument is true or undefined. + """ + directive @defer( + """Deferred when true or undefined.""" + if: Boolean + + """Unique name""" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + """ + Directs the executor to stream plural fields when the \`if\` argument is true or undefined. + """ + directive @stream( + """Stream when true or undefined.""" + if: Boolean + + """Unique name""" + label: String + + """Number of items to return immediately""" + initialCount: Int! + ) on FIELD + type Query { a: A b: B diff --git a/packages/schema/src/buildSchemaFromTypeDefinitions.ts b/packages/schema/src/buildSchemaFromTypeDefinitions.ts index d0e1576139c..04a8b48f636 100644 --- a/packages/schema/src/buildSchemaFromTypeDefinitions.ts +++ b/packages/schema/src/buildSchemaFromTypeDefinitions.ts @@ -1,4 +1,4 @@ -import { extendSchema, buildASTSchema, GraphQLSchema, DocumentNode, ASTNode } from 'graphql'; +import { extendSchema, buildASTSchema, GraphQLSchema, DocumentNode, ASTNode, BuildSchemaOptions } from 'graphql'; import { ITypeDefinitions, GraphQLParseOptions, parseGraphQLSDL } from '@graphql-tools/utils'; @@ -12,12 +12,16 @@ export function buildSchemaFromTypeDefinitions( const document = buildDocumentFromTypeDefinitions(typeDefinitions, parseOptions); const typesAst = filterExtensionDefinitions(document); - const backcompatOptions = { commentDescriptions: true }; - let schema: GraphQLSchema = buildASTSchema(typesAst, backcompatOptions); + const options: BuildSchemaOptions = { + commentDescriptions: true, + experimentalDefer: true, + experimentalStream: true, + }; + let schema: GraphQLSchema = buildASTSchema(typesAst, options); const extensionsAst = extractExtensionDefinitions(document); if (extensionsAst.definitions.length > 0) { - schema = extendSchema(schema, extensionsAst, backcompatOptions); + schema = extendSchema(schema, extensionsAst, options); } return schema; diff --git a/packages/schema/src/types.ts b/packages/schema/src/types.ts index 4280832126d..360c16d19d3 100644 --- a/packages/schema/src/types.ts +++ b/packages/schema/src/types.ts @@ -67,4 +67,12 @@ export interface IExecutableSchemaDefinition { * Additional options for removing unused types from the schema */ pruningOptions?: PruneSchemaOptions; + /** + * Set to `true` to enable support within queries for the experimental `defer` directive + */ + experimentalDefer?: boolean; + /** + * Set to `true` to enable support within queries for the experimental `stream` directive + */ + experimentalStream?: boolean; } diff --git a/packages/stitch/src/stitchSchemas.ts b/packages/stitch/src/stitchSchemas.ts index 379c5b01f2f..d8238f314d9 100644 --- a/packages/stitch/src/stitchSchemas.ts +++ b/packages/stitch/src/stitchSchemas.ts @@ -141,6 +141,8 @@ export function stitchSchemas({ astNode: schemaDefs.schemaDef, extensionASTNodes: schemaDefs.schemaExtensions, extensions: null, + experimentalDefer: true, + experimentalStream: true, }); extensions.forEach(extension => { diff --git a/packages/stitch/src/stitchingInfo.ts b/packages/stitch/src/stitchingInfo.ts index 12a5e99406f..03b3ed3e0d0 100644 --- a/packages/stitch/src/stitchingInfo.ts +++ b/packages/stitch/src/stitchingInfo.ts @@ -348,6 +348,8 @@ export function addStitchingInfo(stitchedSchema: GraphQLSchema, stitchingInfo: S ...stitchedSchema.extensions, stitchingInfo, }, + experimentalDefer: true, + experimentalStream: true, }); } diff --git a/packages/utils/src/addTypes.ts b/packages/utils/src/addTypes.ts index 3254183461e..90053f0f0d0 100644 --- a/packages/utils/src/addTypes.ts +++ b/packages/utils/src/addTypes.ts @@ -80,5 +80,7 @@ export function addTypes( subscription: subscriptionTypeName != null ? (typeMap[subscriptionTypeName] as GraphQLObjectType) : undefined, types: Object.keys(typeMap).map(typeName => typeMap[typeName]), directives, + experimentalDefer: true, + experimentalStream: true, }); } diff --git a/packages/utils/src/mapSchema.ts b/packages/utils/src/mapSchema.ts index 656d8424127..37fb406b159 100644 --- a/packages/utils/src/mapSchema.ts +++ b/packages/utils/src/mapSchema.ts @@ -88,6 +88,8 @@ export function mapSchema(schema: GraphQLSchema, schemaMapper: SchemaMapper = {} subscription: newSubscriptionTypeName != null ? (typeMap[newSubscriptionTypeName] as GraphQLObjectType) : undefined, types: Object.keys(typeMap).map(typeName => typeMap[typeName]), directives, + experimentalDefer: true, + experimentalStream: true, }); } diff --git a/yarn.lock b/yarn.lock index 16ccd2c33cc..6f26b8c9af4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6782,10 +6782,10 @@ graphql-upload@^8.0.2: http-errors "^1.7.3" object-path "^0.11.4" -graphql@15.3.0, graphql@^14.5.3: - version "15.3.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" - integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== +graphql@15.3.0, graphql@^14.5.3, "graphql@npm:graphql-experimental": + version "4.0.1" + resolved "https://registry.yarnpkg.com/graphql-experimental/-/graphql-experimental-4.0.1.tgz#65a2bb1573d20685be62493287c31273419b99ce" + integrity sha512-Z9GHafFdxDMq7sGq+iUdfPCMeTphc/LOOjt2EB9RNDB0j1ShsIg8Bp7PV42oKw3C1ccLvguQaw1DeuldsQuuxw== gray-matter@^4.0.2: version "4.0.2"