From 36191f73f5cfb2bc566aba3f28b6ece49c53f8ef Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Thu, 25 Aug 2022 15:30:35 +0200 Subject: [PATCH 1/5] Fix fragments extensions nuking @defer --- internals-js/src/operations.ts | 2 +- .../src/__tests__/buildPlan.defer.test.ts | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/internals-js/src/operations.ts b/internals-js/src/operations.ts index 2cd978de7e..dac87b33bc 100644 --- a/internals-js/src/operations.ts +++ b/internals-js/src/operations.ts @@ -1894,7 +1894,7 @@ class FragmentSpreadSelection extends FragmentSelection { } const expandedSubSelections = this.selectionSet.expandFragments(names, updateSelectionSetFragments); - return sameType(this._element.parentType, this.namedFragment.typeCondition) + return sameType(this._element.parentType, this.namedFragment.typeCondition) && this._element.appliedDirectives.length === 0 ? expandedSubSelections.selections() : new InlineFragmentSelection(this._element, expandedSubSelections); } diff --git a/query-planner-js/src/__tests__/buildPlan.defer.test.ts b/query-planner-js/src/__tests__/buildPlan.defer.test.ts index 2d23a8b8f3..5db664e459 100644 --- a/query-planner-js/src/__tests__/buildPlan.defer.test.ts +++ b/query-planner-js/src/__tests__/buildPlan.defer.test.ts @@ -3088,3 +3088,89 @@ test('defer when some interface has different definitions in different subgraphs } `); }); + +describe('named fragments', () => { + test('simple use', () => { + const subgraph1 = { + name: 'Subgraph1', + typeDefs: gql` + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + } + ` + } + + const subgraph2 = { + name: 'Subgraph2', + typeDefs: gql` + type T @key(fields: "id") { + id: ID! + x: Int + y: Int + } + ` + } + + const [api, queryPlanner] = composeAndCreatePlannerWithDefer(subgraph1, subgraph2); + const operation = operationFromDocument(api, gql` + { + t { + ...TestFragment @defer + } + } + + fragment TestFragment on T { + x + y + } + `); + + const queryPlan = queryPlanner.buildQueryPlan(operation); + expect(queryPlan).toMatchInlineSnapshot(` + QueryPlan { + Defer { + Primary { + : + Fetch(service: "Subgraph1", id: 0) { + { + t { + __typename + id + } + } + } + }, [ + Deferred(depends: [0], path: "t") { + { + ... on T { + x + y + } + }: + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + x + y + } + } + }, + } + }, + ] + }, + } + `); + }); +}); From 52044125fe8e227b2807d74ba4952d144753e76a Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Thu, 25 Aug 2022 16:05:43 +0200 Subject: [PATCH 2/5] Minor @defer updates This address most of the comment from @glasser on #1958, namely: - it removes/update some outdated TODOs. - it fixup the definition of @defer/@stream to match current spec. - makes serialized query plan for condition hopefully a tad easier to parse (solely impact unit tests readability). - adds a test to ensure that if the same field is queries both "normally" and deferred, we do query it twice (that's what spec specifies). --- .../src/__tests__/definitions.test.ts | 17 +- internals-js/src/definitions.ts | 18 +- .../src/__tests__/buildPlan.defer.test.ts | 737 +++++++++++------- .../queryPlanSerializer.ts | 8 +- 4 files changed, 463 insertions(+), 317 deletions(-) diff --git a/internals-js/src/__tests__/definitions.test.ts b/internals-js/src/__tests__/definitions.test.ts index 45e0106d48..f2be9cb450 100644 --- a/internals-js/src/__tests__/definitions.test.ts +++ b/internals-js/src/__tests__/definitions.test.ts @@ -817,7 +817,7 @@ test('correctly convert to a graphQL-js schema', () => { expect(printGraphQLjsSchema(graphqQLSchema)).toMatchString(sdl); }); -test('Conversion to graphQL-js schema can optionally include @defer definition', () => { +test('Conversion to graphQL-js schema can optionally include @defer and/or @streams definition(s)', () => { const sdl = ` type Query { x: Int @@ -825,9 +825,18 @@ test('Conversion to graphQL-js schema can optionally include @defer definition', `; const schema = parseSchema(sdl); - const graphqQLSchema = schema.toGraphQLJSSchema({ includeDefer: true }); - expect(printGraphQLjsSchema(graphqQLSchema)).toMatchString(` - directive @defer(label: String, if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT + expect(printGraphQLjsSchema(schema.toGraphQLJSSchema({ includeDefer: true }))).toMatchString(` + directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + x: Int + } + `); + + expect(printGraphQLjsSchema(schema.toGraphQLJSSchema({ includeDefer: true, includeStream: true }))).toMatchString(` + directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + directive @stream(label: String, initialCount: Int = 0, if: Boolean! = true) on FIELD type Query { x: Int diff --git a/internals-js/src/definitions.ts b/internals-js/src/definitions.ts index c22f15b358..2a741c4957 100644 --- a/internals-js/src/definitions.ts +++ b/internals-js/src/definitions.ts @@ -1128,15 +1128,16 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[] locations: [DirectiveLocation.SCALAR], argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] }) }), - // TODO: currently inconditionally adding @defer as the list of built-in. It's probably fine, but double check if we want to not do so when @defer-support is - // not enabled or something (it would probably be hard to handle it at that point anyway but well...). + // Note that @defer and @stream a inconditionally added to `Schema` even if they are technically "optional" built-in. _But_, + // the `Schema#toGraphQLJSSchema` method has an option to decide if @defer/@stream should be included or not in the resulting + // schema, which is how the gateway and router can, at runtime, decide to include or not include them based on actual support. createDirectiveSpecification({ name: 'defer', locations: [DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT], argumentFct: (schema) => ({ args: [ { name: 'label', type: schema.stringType() }, - { name: 'if', type: schema.booleanType() }, + { name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true }, ], errors: [], }) @@ -1151,7 +1152,7 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[] args: [ { name: 'label', type: schema.stringType() }, { name: 'initialCount', type: schema.intType(), defaultValue: 0 }, - { name: 'if', type: schema.booleanType() }, + { name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true }, ], errors: [], }) @@ -1159,9 +1160,6 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[] ]; export type DeferDirectiveArgs = { - // TODO: we currently do not support variables for the defer label. Passing a label in a variable - // feels like a weird use case in the first place, but we should probably fix this nonetheless (or - // if we decide to have it be a known limitations, we should at least reject it cleanly). label?: string, if?: boolean | Variable, } @@ -1299,8 +1297,9 @@ export class Schema { return nodes; } - toGraphQLJSSchema(config?: { includeDefer?: boolean }): GraphQLSchema { + toGraphQLJSSchema(config?: { includeDefer?: boolean, includeStream?: boolean }): GraphQLSchema { const includeDefer = config?.includeDefer ?? false; + const includeStream = config?.includeStream ?? false; let ast = this.toAST(); @@ -1312,6 +1311,9 @@ export class Schema { if (includeDefer) { additionalNodes.push(this.deferDirective().toAST()); } + if (includeStream) { + additionalNodes.push(this.streamDirective().toAST()); + } if (additionalNodes.length > 0) { ast = { kind: Kind.DOCUMENT, diff --git a/query-planner-js/src/__tests__/buildPlan.defer.test.ts b/query-planner-js/src/__tests__/buildPlan.defer.test.ts index 5db664e459..44e4a3f273 100644 --- a/query-planner-js/src/__tests__/buildPlan.defer.test.ts +++ b/query-planner-js/src/__tests__/buildPlan.defer.test.ts @@ -2458,70 +2458,73 @@ describe('defer with conditions', () => { expect(plan).toMatchInlineSnapshot(` QueryPlan { Condition(if: $cond) { - Defer { - Primary { - { - t { - x - } - }: - Fetch(service: "Subgraph1", id: 0) { + Then { + Defer { + Primary { { t { - __typename x - id } - } - } - }, [ - Deferred(depends: [0], path: "t") { - { - y }: - Flatten(path: "t") { - Fetch(service: "Subgraph2") { - { - ... on T { - __typename - id - } - } => - { - ... on T { - y - } + Fetch(service: "Subgraph1", id: 0) { + { + t { + __typename + x + id } - }, - } - }, - ] - }, - Sequence { - Fetch(service: "Subgraph1") { - { - t { - __typename - id - x + } } - } - }, - Flatten(path: "t") { - Fetch(service: "Subgraph2") { + }, [ + Deferred(depends: [0], path: "t") { + { + y + }: + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + y + } + } + }, + } + }, + ] + } + } Else { + Sequence { + Fetch(service: "Subgraph1") { { - ... on T { + t { __typename id - } - } => - { - ... on T { - y + x } } }, - }, + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + y + } + } + }, + }, + } } }, } @@ -2564,50 +2567,53 @@ describe('defer with conditions', () => { expect(plan).toMatchInlineSnapshot(` QueryPlan { Condition(if: $cond) { - Defer { - Primary { - { - t { - x - } - }: - Fetch(service: "Subgraph1", id: 0) { + Then { + Defer { + Primary { { t { - __typename x - id } - } - } - }, [ - Deferred(depends: [0], path: "t") { - { - y }: - Flatten(path: "t") { - Fetch(service: "Subgraph1") { - { - ... on T { - __typename - id - } - } => - { - ... on T { - y - } + Fetch(service: "Subgraph1", id: 0) { + { + t { + __typename + x + id } - }, + } + } + }, [ + Deferred(depends: [0], path: "t") { + { + y + }: + Flatten(path: "t") { + Fetch(service: "Subgraph1") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + y + } + } + }, + } + }, + ] + } + } Else { + Fetch(service: "Subgraph1") { + { + t { + x + y } - }, - ] - }, - Fetch(service: "Subgraph1") { - { - t { - x - y } } } @@ -2681,34 +2687,83 @@ describe('defer with conditions', () => { expect(plan).toMatchInlineSnapshot(` QueryPlan { Condition(if: $cond1) { - Condition(if: $cond2) { - Defer { - Primary { - { - t { - x - } - }: - Fetch(service: "Subgraph1", id: 0) { - { - t { - __typename - x - id - } - } - } - }, [ - Deferred(depends: [0], path: "t", label: "bar") { - Defer { - Primary { + Then { + Condition(if: $cond2) { + Then { + Defer { + Primary { + { + t { + x + } + }: + Fetch(service: "Subgraph1", id: 0) { { - u { - a + t { + __typename + x + id } + } + } + }, [ + Deferred(depends: [0], path: "t", label: "bar") { + Defer { + Primary { + { + u { + a + } + }: + Flatten(path: "t") { + Fetch(service: "Subgraph1", id: 1) { + { + ... on T { + __typename + id + } + } => + { + ... on T { + u { + __typename + a + id + } + } + } + }, + } + }, [ + Deferred(depends: [1], path: "t.u") { + { + b + }: + Flatten(path: "t.u") { + Fetch(service: "Subgraph3") { + { + ... on U { + __typename + id + } + } => + { + ... on U { + b + } + } + }, + } + }, + ] + } + }, + Deferred(depends: [0], path: "t", label: "foo") { + { + y }: Flatten(path: "t") { - Fetch(service: "Subgraph1", id: 1) { + Fetch(service: "Subgraph2") { { ... on T { __typename @@ -2717,175 +2772,187 @@ describe('defer with conditions', () => { } => { ... on T { - u { - __typename - a - id - } + y } } }, } - }, [ - Deferred(depends: [1], path: "t.u") { - { - b - }: - Flatten(path: "t.u") { - Fetch(service: "Subgraph3") { - { - ... on U { - __typename - id - } - } => - { - ... on U { - b - } - } - }, + }, + ] + } + } Else { + Defer { + Primary { + { + t { + x + u { + a } - }, - ] - } - }, - Deferred(depends: [0], path: "t", label: "foo") { - { - y - }: - Flatten(path: "t") { - Fetch(service: "Subgraph2") { + } + }: + Fetch(service: "Subgraph1", id: 0) { { - ... on T { + t { __typename + x id + u { + __typename + a + id + } } - } => + } + } + }, [ + Deferred(depends: [0], path: "t", label: "foo") { { - ... on T { - y - } + y + }: + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + y + } + } + }, } }, - } - }, - ] - }, - Defer { - Primary { - { - t { - x - u { - a - } - } - }: - Fetch(service: "Subgraph1", id: 0) { - { - t { - __typename - x - id - u { - __typename - a - id + Deferred(depends: [0], path: "t.u") { + { + b + }: + Flatten(path: "t.u") { + Fetch(service: "Subgraph3") { + { + ... on U { + __typename + id + } + } => + { + ... on U { + b + } + } + }, } - } - } + }, + ] } - }, [ - Deferred(depends: [0], path: "t", label: "foo") { - { - y - }: - Flatten(path: "t") { - Fetch(service: "Subgraph2") { - { - ... on T { - __typename - id - } - } => - { - ... on T { - y - } + } + } + } Else { + Condition(if: $cond2) { + Then { + Defer { + Primary { + { + t { + x + y } - }, - } - }, - Deferred(depends: [0], path: "t.u") { - { - b - }: - Flatten(path: "t.u") { - Fetch(service: "Subgraph3") { - { - ... on U { - __typename - id + }: + Sequence { + Fetch(service: "Subgraph1", id: 0) { + { + t { + __typename + id + x + } } - } => + }, + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + y + } + } + }, + }, + } + }, [ + Deferred(depends: [0], path: "t", label: "bar") { { - ... on U { + u { + a b } + }: + Sequence { + Flatten(path: "t") { + Fetch(service: "Subgraph1") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + u { + __typename + id + a + } + } + } + }, + }, + Flatten(path: "t.u") { + Fetch(service: "Subgraph3") { + { + ... on U { + __typename + id + } + } => + { + ... on U { + b + } + } + }, + }, } }, - } - }, - ] - } - }, - Condition(if: $cond2) { - Defer { - Primary { - { - t { - x - y - } - }: + ] + } + } Else { Sequence { - Fetch(service: "Subgraph1", id: 0) { + Fetch(service: "Subgraph1") { { t { __typename id x - } - } - }, - Flatten(path: "t") { - Fetch(service: "Subgraph2") { - { - ... on T { + u { __typename id - } - } => - { - ... on T { - y + a } } - }, - }, - } - }, [ - Deferred(depends: [0], path: "t", label: "bar") { - { - u { - a - b } - }: - Sequence { + }, + Parallel { Flatten(path: "t") { - Fetch(service: "Subgraph1") { + Fetch(service: "Subgraph2") { { ... on T { __typename @@ -2894,11 +2961,7 @@ describe('defer with conditions', () => { } => { ... on T { - u { - __typename - id - a - } + y } } }, @@ -2918,57 +2981,9 @@ describe('defer with conditions', () => { } }, }, - } - }, - ] - }, - Sequence { - Fetch(service: "Subgraph1") { - { - t { - __typename - id - x - u { - __typename - id - a - } - } - } - }, - Parallel { - Flatten(path: "t") { - Fetch(service: "Subgraph2") { - { - ... on T { - __typename - id - } - } => - { - ... on T { - y - } - } - }, - }, - Flatten(path: "t.u") { - Fetch(service: "Subgraph3") { - { - ... on U { - __typename - id - } - } => - { - ... on U { - b - } - } }, - }, - }, + } + } } } }, @@ -3173,4 +3188,120 @@ describe('named fragments', () => { } `); }); + + test('expands into the same field deferred and not deferred', () => { + const subgraph1 = { + name: 'Subgraph1', + typeDefs: gql` + type Query { + t: T + } + + type T @key(fields: "id") { + id: ID! + } + ` + } + + const subgraph2 = { + name: 'Subgraph2', + typeDefs: gql` + type T @key(fields: "id") { + id: ID! + x: Int + y: Int + z: Int + } + ` + } + + const [api, queryPlanner] = composeAndCreatePlannerWithDefer(subgraph1, subgraph2); + const operation = operationFromDocument(api, gql` + { + t { + ...Fragment1 + ...Fragment2 @defer + } + } + + fragment Fragment1 on T { + x + y + } + + fragment Fragment2 on T { + y + z + } + `); + + // Field 'y' is queried twice, both in the deferred and non-deferred section. The spec says that + // means the field is requested twice, so ensures that's what we do. + const queryPlan = queryPlanner.buildQueryPlan(operation); + expect(queryPlan).toMatchInlineSnapshot(` + QueryPlan { + Defer { + Primary { + { + t { + x + y + } + }: + Sequence { + Fetch(service: "Subgraph1", id: 0) { + { + t { + __typename + id + } + } + }, + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + x + y + } + } + }, + }, + } + }, [ + Deferred(depends: [0], path: "t") { + { + ... on T { + y + z + } + }: + Flatten(path: "t") { + Fetch(service: "Subgraph2") { + { + ... on T { + __typename + id + } + } => + { + ... on T { + y + z + } + } + }, + } + }, + ] + }, + } + `); + }); }); diff --git a/query-planner-js/src/snapshotSerializers/queryPlanSerializer.ts b/query-planner-js/src/snapshotSerializers/queryPlanSerializer.ts index 29db8bb052..40f0fc21ba 100644 --- a/query-planner-js/src/snapshotSerializers/queryPlanSerializer.ts +++ b/query-planner-js/src/snapshotSerializers/queryPlanSerializer.ts @@ -96,11 +96,15 @@ function printNode( break; case 'Condition': if (node.ifClause) { + const indentationInner = indentationNext + config.indent; if (node.elseClause) { result += `Condition(if: \$${node.condition}) {` + config.spacingOuter + - indentationNext + printNode(node.ifClause, config, indentationNext, depth, refs, printer) + ',' + config.spacingOuter + - indentationNext + printNode(node.elseClause, config, indentationNext, depth, refs, printer) + config.spacingOuter + + indentationNext + `Then {` + config.spacingOuter + + indentationInner + printNode(node.ifClause, config, indentationInner, depth, refs, printer) + config.spacingOuter + + indentationNext + `} Else {` + config.spacingOuter + + indentationInner + printNode(node.elseClause, config, indentationInner, depth, refs, printer) + config.spacingOuter + + indentationNext + `}` + config.spacingOuter + indentation + '}' } else { result += From 28aaa034503168b60346581e5616adfe2cc356da Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Thu, 25 Aug 2022 16:11:06 +0200 Subject: [PATCH 3/5] Tweak the name of the config used to enable @defer in the QP --- query-planner-js/src/__tests__/buildPlan.defer.test.ts | 2 +- query-planner-js/src/buildPlan.ts | 2 +- query-planner-js/src/config.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/query-planner-js/src/__tests__/buildPlan.defer.test.ts b/query-planner-js/src/__tests__/buildPlan.defer.test.ts index 44e4a3f273..379b4ac45f 100644 --- a/query-planner-js/src/__tests__/buildPlan.defer.test.ts +++ b/query-planner-js/src/__tests__/buildPlan.defer.test.ts @@ -4,7 +4,7 @@ import { QueryPlanner } from '@apollo/query-planner'; import { composeAndCreatePlanner, composeAndCreatePlannerWithOptions } from "./buildPlan.test"; function composeAndCreatePlannerWithDefer(...services: ServiceDefinition[]): [Schema, QueryPlanner] { - return composeAndCreatePlannerWithOptions(services, { deferStreamSupport: { enableDefer : true }}); + return composeAndCreatePlannerWithOptions(services, { incrementalDelivery: { enableDefer : true }}); } describe('handles simple @defer', () => { diff --git a/query-planner-js/src/buildPlan.ts b/query-planner-js/src/buildPlan.ts index ca10159d58..f860cd76c9 100644 --- a/query-planner-js/src/buildPlan.ts +++ b/query-planner-js/src/buildPlan.ts @@ -1911,7 +1911,7 @@ export function computeQueryPlan({ let assignedDeferLabels: Set | undefined = undefined; let hasDefers: boolean = false; let deferConditions: SetMultiMap | undefined = undefined; - if (config.deferStreamSupport.enableDefer) { + if (config.incrementalDelivery.enableDefer) { ({ operation, hasDefers, assignedDeferLabels, deferConditions } = operation.withNormalizedDefer()); } else { // If defer is not enabled, we remove all @defer from the query. This feels cleaner do this once here than diff --git a/query-planner-js/src/config.ts b/query-planner-js/src/config.ts index 4482c3b3a2..2c70453afc 100644 --- a/query-planner-js/src/config.ts +++ b/query-planner-js/src/config.ts @@ -23,7 +23,7 @@ export type QueryPlannerConfig = { // new `passthroughSubgraphs` option that is the list of subgraph to which we can pass-through some @defer // (and it would be empty by default). Similarly, once we support @stream, grouping the options here will // make sense too. - deferStreamSupport?: { + incrementalDelivery?: { /** * Enables @defer support by the query planner. * @@ -41,7 +41,7 @@ export function enforceQueryPlannerConfigDefaults( return { exposeDocumentNodeInFetchNode: true, reuseQueryFragments: true, - deferStreamSupport: { + incrementalDelivery: { enableDefer: false, }, ...config, From 7ea2795edf568af672446f39d0899dc60d32a239 Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Fri, 26 Aug 2022 10:29:48 +0200 Subject: [PATCH 4/5] Adds changelog entries --- gateway-js/CHANGELOG.md | 1 + query-planner-js/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/gateway-js/CHANGELOG.md b/gateway-js/CHANGELOG.md index 4bb8508769..c1222b263c 100644 --- a/gateway-js/CHANGELOG.md +++ b/gateway-js/CHANGELOG.md @@ -4,6 +4,7 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The ## vNext +- Fix issue where fragment expansion can erase applied directives (most notably `@defer`) [PR #2093](https://github.com/apollographql/federation/pull/2093). - Fix abnormally high memory usage when extracting subgraphs for some fed1 supergraphs (and small other memory footprint improvements) [PR #2089](https://github.com/apollographql/federation/pull/2089). - Fix issue with fragment reusing code something mistakenly re-expanding fragments [PR #2098](https://github.com/apollographql/federation/pull/2098). - Fix issue when type is only reachable through a @provides [PR #2083](https://github.com/apollographql/federation/pull/2083). diff --git a/query-planner-js/CHANGELOG.md b/query-planner-js/CHANGELOG.md index 5b3fb49159..f86a677d12 100644 --- a/query-planner-js/CHANGELOG.md +++ b/query-planner-js/CHANGELOG.md @@ -3,6 +3,7 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The Federation v0.x equivalent for this package can be found [here](https://github.com/apollographql/federation/blob/version-0.x/query-planner-js/CHANGELOG.md) on the `version-0.x` branch of this repo. +- Fix issue where fragment expansion can erase applied directives (most notably `@defer`) [PR #2093](https://github.com/apollographql/federation/pull/2093). - Fix issue with fragment reusing code something mistakenly re-expanding fragments [PR #2098](https://github.com/apollographql/federation/pull/2098). ## 2.1.0-alpha.4 From e4acca8eeb19d75765f0a0722ffa4dba8ebf0720 Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Mon, 29 Aug 2022 18:50:19 +0200 Subject: [PATCH 5/5] fix bad english --- internals-js/src/definitions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internals-js/src/definitions.ts b/internals-js/src/definitions.ts index 2a741c4957..dcb8fc8b43 100644 --- a/internals-js/src/definitions.ts +++ b/internals-js/src/definitions.ts @@ -1128,7 +1128,7 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[] locations: [DirectiveLocation.SCALAR], argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] }) }), - // Note that @defer and @stream a inconditionally added to `Schema` even if they are technically "optional" built-in. _But_, + // Note that @defer and @stream are unconditionally added to `Schema` even if they are technically "optional" built-in. _But_, // the `Schema#toGraphQLJSSchema` method has an option to decide if @defer/@stream should be included or not in the resulting // schema, which is how the gateway and router can, at runtime, decide to include or not include them based on actual support. createDirectiveSpecification({