@apollo/gateway 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__generated__/graphqlTypes.d.ts +1 -0
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/dataRewrites.d.ts +5 -0
- package/dist/dataRewrites.d.ts.map +1 -0
- package/dist/dataRewrites.js +103 -0
- package/dist/dataRewrites.js.map +1 -0
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +41 -110
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/index.js +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/index.js.map +1 -1
- package/package.json +4 -4
- package/src/__generated__/graphqlTypes.ts +4 -3
- package/src/__tests__/buildQueryPlan.test.ts +0 -1
- package/src/__tests__/executeQueryPlan.introspection.test.ts +140 -0
- package/src/__tests__/executeQueryPlan.test.ts +512 -4
- package/src/__tests__/gateway/buildService.test.ts +2 -2
- package/src/__tests__/gateway/supergraphSdl.test.ts +2 -2
- package/src/__tests__/integration/complex-key.test.ts +4 -4
- package/src/__tests__/integration/list-key.test.ts +2 -2
- package/src/__tests__/integration/multiple-key.test.ts +2 -2
- package/src/__tests__/integration/requires.test.ts +2 -2
- package/src/__tests__/integration/single-service.test.ts +1 -1
- package/src/__tests__/integration/value-types.test.ts +13 -16
- package/src/dataRewrites.ts +130 -0
- package/src/executeQueryPlan.ts +57 -135
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import gql from 'graphql-tag';
|
|
2
|
+
import { getFederatedTestingSchema, ServiceDefinitionModule } from './execution-utils';
|
|
3
|
+
import { Operation, parseOperation, Schema } from "@apollo/federation-internals";
|
|
4
|
+
import { QueryPlan } from '@apollo/query-planner';
|
|
5
|
+
import { LocalGraphQLDataSource } from '../datasources';
|
|
6
|
+
import { GatewayExecutionResult, GatewayGraphQLRequestContext } from '@apollo/server-gateway-interface';
|
|
7
|
+
import { buildOperationContext } from '../operationContext';
|
|
8
|
+
import { executeQueryPlan } from '../executeQueryPlan';
|
|
9
|
+
|
|
10
|
+
function buildRequestContext(variables: Record<string, any>): GatewayGraphQLRequestContext {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
return {
|
|
14
|
+
cache: undefined as any,
|
|
15
|
+
context: {},
|
|
16
|
+
request: {
|
|
17
|
+
variables,
|
|
18
|
+
},
|
|
19
|
+
metrics: {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function executePlan(
|
|
24
|
+
queryPlan: QueryPlan,
|
|
25
|
+
operation: Operation,
|
|
26
|
+
schema: Schema,
|
|
27
|
+
serviceMap: { [serviceName: string]: LocalGraphQLDataSource },
|
|
28
|
+
variables: Record<string, any> = {},
|
|
29
|
+
): Promise<GatewayExecutionResult> {
|
|
30
|
+
const apiSchema = schema.toAPISchema();
|
|
31
|
+
const operationContext = buildOperationContext({
|
|
32
|
+
schema: apiSchema.toGraphQLJSSchema(),
|
|
33
|
+
operationDocument: gql`${operation.toString()}`,
|
|
34
|
+
});
|
|
35
|
+
return executeQueryPlan(
|
|
36
|
+
queryPlan,
|
|
37
|
+
serviceMap,
|
|
38
|
+
buildRequestContext(variables),
|
|
39
|
+
operationContext,
|
|
40
|
+
schema.toGraphQLJSSchema(),
|
|
41
|
+
apiSchema,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('handling of introspection queries', () => {
|
|
46
|
+
const typeDefs: ServiceDefinitionModule[] = [
|
|
47
|
+
{
|
|
48
|
+
name: 'S1',
|
|
49
|
+
typeDefs: gql`
|
|
50
|
+
type Query {
|
|
51
|
+
t: [T]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface T {
|
|
55
|
+
id: ID!
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type T1 implements T @key(fields: "id") {
|
|
59
|
+
id: ID!
|
|
60
|
+
a1: Int
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type T2 implements T @key(fields: "id") {
|
|
64
|
+
id: ID!
|
|
65
|
+
a2: Int
|
|
66
|
+
}
|
|
67
|
+
`,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema(typeDefs);
|
|
71
|
+
|
|
72
|
+
it('it handles aliases on introspection fields', async () => {
|
|
73
|
+
const operation = parseOperation(schema, `
|
|
74
|
+
{
|
|
75
|
+
myAlias: __type(name: "T1") {
|
|
76
|
+
kind
|
|
77
|
+
name
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
83
|
+
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
84
|
+
expect(response.errors).toBeUndefined();
|
|
85
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
86
|
+
Object {
|
|
87
|
+
"myAlias": Object {
|
|
88
|
+
"kind": "OBJECT",
|
|
89
|
+
"name": "T1",
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('it handles aliases inside introspection fields', async () => {
|
|
96
|
+
const operation = parseOperation(schema, `
|
|
97
|
+
{
|
|
98
|
+
__type(name: "T1") {
|
|
99
|
+
myKind: kind
|
|
100
|
+
name
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`);
|
|
104
|
+
|
|
105
|
+
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
106
|
+
const response = await executePlan(queryPlan, operation, schema, serviceMap);
|
|
107
|
+
expect(response.errors).toBeUndefined();
|
|
108
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
109
|
+
Object {
|
|
110
|
+
"__type": Object {
|
|
111
|
+
"myKind": "OBJECT",
|
|
112
|
+
"name": "T1",
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
`);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('it handles variables passed to introspection fields', async () => {
|
|
119
|
+
const operation = parseOperation(schema, `
|
|
120
|
+
query ($name: String!) {
|
|
121
|
+
__type(name: $name) {
|
|
122
|
+
kind
|
|
123
|
+
name
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`);
|
|
127
|
+
|
|
128
|
+
const queryPlan = queryPlanner.buildQueryPlan(operation);
|
|
129
|
+
const response = await executePlan(queryPlan, operation, schema, serviceMap, { name: "T1" });
|
|
130
|
+
expect(response.errors).toBeUndefined();
|
|
131
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
132
|
+
Object {
|
|
133
|
+
"__type": Object {
|
|
134
|
+
"kind": "OBJECT",
|
|
135
|
+
"name": "T1",
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
`);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -3830,7 +3830,7 @@ describe('executeQueryPlan', () => {
|
|
|
3830
3830
|
name: 'S1',
|
|
3831
3831
|
typeDefs: gql`
|
|
3832
3832
|
extend schema
|
|
3833
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
3833
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
|
3834
3834
|
|
|
3835
3835
|
type Query {
|
|
3836
3836
|
iFromS1: I
|
|
@@ -3876,7 +3876,7 @@ describe('executeQueryPlan', () => {
|
|
|
3876
3876
|
name: 'S2',
|
|
3877
3877
|
typeDefs: gql`
|
|
3878
3878
|
extend schema
|
|
3879
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
3879
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject"])
|
|
3880
3880
|
|
|
3881
3881
|
type Query {
|
|
3882
3882
|
iFromS2: I
|
|
@@ -4342,7 +4342,7 @@ describe('executeQueryPlan', () => {
|
|
|
4342
4342
|
name: 'products',
|
|
4343
4343
|
typeDefs: gql`
|
|
4344
4344
|
extend schema
|
|
4345
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
4345
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"])
|
|
4346
4346
|
|
|
4347
4347
|
type Query {
|
|
4348
4348
|
products: [Product!]!
|
|
@@ -4391,7 +4391,7 @@ describe('executeQueryPlan', () => {
|
|
|
4391
4391
|
name: 'reviews',
|
|
4392
4392
|
typeDefs: gql`
|
|
4393
4393
|
extend schema
|
|
4394
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
4394
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key", "@interfaceObject"])
|
|
4395
4395
|
|
|
4396
4396
|
type Query {
|
|
4397
4397
|
allReviewedProducts: [Product!]!
|
|
@@ -4600,6 +4600,514 @@ describe('executeQueryPlan', () => {
|
|
|
4600
4600
|
}
|
|
4601
4601
|
`);
|
|
4602
4602
|
});
|
|
4603
|
+
|
|
4604
|
+
test('handles querying @interfaceObject from a specific implementation', async () => {
|
|
4605
|
+
const s1 = {
|
|
4606
|
+
name: 's1',
|
|
4607
|
+
typeDefs: gql`
|
|
4608
|
+
extend schema
|
|
4609
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
|
|
4610
|
+
|
|
4611
|
+
type Query {
|
|
4612
|
+
ts: [T!]!
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
interface I {
|
|
4616
|
+
id: ID!
|
|
4617
|
+
}
|
|
4618
|
+
|
|
4619
|
+
type T implements I @key(fields: "id") {
|
|
4620
|
+
id: ID!
|
|
4621
|
+
}
|
|
4622
|
+
`,
|
|
4623
|
+
resolvers: {
|
|
4624
|
+
Query: {
|
|
4625
|
+
ts: () => [ { id: '2' }, { id: '4' }, { id: '1' } ]
|
|
4626
|
+
},
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
const s2 = {
|
|
4631
|
+
name: 's2',
|
|
4632
|
+
typeDefs: gql`
|
|
4633
|
+
extend schema
|
|
4634
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject", "@external", "@requires"])
|
|
4635
|
+
|
|
4636
|
+
type I @key(fields: "id") @interfaceObject {
|
|
4637
|
+
id: ID!
|
|
4638
|
+
v: String
|
|
4639
|
+
}
|
|
4640
|
+
`,
|
|
4641
|
+
resolvers: {
|
|
4642
|
+
I: {
|
|
4643
|
+
__resolveReference(ref: any) {
|
|
4644
|
+
return {
|
|
4645
|
+
...ref,
|
|
4646
|
+
v: `id=${ref.id}`
|
|
4647
|
+
};
|
|
4648
|
+
},
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
4654
|
+
|
|
4655
|
+
let operation = parseOp(`
|
|
4656
|
+
{
|
|
4657
|
+
ts {
|
|
4658
|
+
v
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
`, schema);
|
|
4662
|
+
|
|
4663
|
+
let queryPlan = buildPlan(operation, queryPlanner);
|
|
4664
|
+
const expectedPlan = `
|
|
4665
|
+
QueryPlan {
|
|
4666
|
+
Sequence {
|
|
4667
|
+
Fetch(service: "s1") {
|
|
4668
|
+
{
|
|
4669
|
+
ts {
|
|
4670
|
+
__typename
|
|
4671
|
+
id
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
},
|
|
4675
|
+
Flatten(path: "ts.@") {
|
|
4676
|
+
Fetch(service: "s2") {
|
|
4677
|
+
{
|
|
4678
|
+
... on T {
|
|
4679
|
+
__typename
|
|
4680
|
+
id
|
|
4681
|
+
}
|
|
4682
|
+
} =>
|
|
4683
|
+
{
|
|
4684
|
+
... on I {
|
|
4685
|
+
v
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
},
|
|
4689
|
+
},
|
|
4690
|
+
},
|
|
4691
|
+
}
|
|
4692
|
+
`;
|
|
4693
|
+
expect(queryPlan).toMatchInlineSnapshot(expectedPlan);
|
|
4694
|
+
|
|
4695
|
+
let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
4696
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
4697
|
+
Object {
|
|
4698
|
+
"ts": Array [
|
|
4699
|
+
Object {
|
|
4700
|
+
"v": "id=2",
|
|
4701
|
+
},
|
|
4702
|
+
Object {
|
|
4703
|
+
"v": "id=4",
|
|
4704
|
+
},
|
|
4705
|
+
Object {
|
|
4706
|
+
"v": "id=1",
|
|
4707
|
+
},
|
|
4708
|
+
],
|
|
4709
|
+
}
|
|
4710
|
+
`);
|
|
4711
|
+
});
|
|
4712
|
+
|
|
4713
|
+
test('handles querying @interfaceObject from a specific implementation (even when the subgraph does not have the corresponding interface)', async () => {
|
|
4714
|
+
const s1 = {
|
|
4715
|
+
name: 's1',
|
|
4716
|
+
typeDefs: gql`
|
|
4717
|
+
extend schema
|
|
4718
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
|
|
4719
|
+
|
|
4720
|
+
type Query {
|
|
4721
|
+
ts: [T!]!
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
type T @key(fields: "id", resolvable: false) {
|
|
4725
|
+
id: ID!
|
|
4726
|
+
}
|
|
4727
|
+
`,
|
|
4728
|
+
resolvers: {
|
|
4729
|
+
Query: {
|
|
4730
|
+
ts: () => [ { id: '2' }, { id: '4' }, { id: '1' } ]
|
|
4731
|
+
},
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
const s2 = {
|
|
4736
|
+
name: 's2',
|
|
4737
|
+
typeDefs: gql`
|
|
4738
|
+
extend schema
|
|
4739
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject"])
|
|
4740
|
+
|
|
4741
|
+
interface I @key(fields: "id") {
|
|
4742
|
+
id: ID!
|
|
4743
|
+
required: String
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
type T implements I @key(fields: "id") {
|
|
4747
|
+
id: ID!
|
|
4748
|
+
required: String
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
`,
|
|
4752
|
+
resolvers: {
|
|
4753
|
+
I: {
|
|
4754
|
+
__resolveReference(ref: any) {
|
|
4755
|
+
return [
|
|
4756
|
+
{ id: '1', __typename: "T", required: "r1" },
|
|
4757
|
+
{ id: '2', __typename: "T", required: "r2" },
|
|
4758
|
+
{ id: '3', __typename: "T", required: "r3" },
|
|
4759
|
+
{ id: '4', __typename: "T", required: "r4" },
|
|
4760
|
+
].find(({id}) => id === ref.id);
|
|
4761
|
+
},
|
|
4762
|
+
},
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4766
|
+
const s3 = {
|
|
4767
|
+
name: 's3',
|
|
4768
|
+
typeDefs: gql`
|
|
4769
|
+
extend schema
|
|
4770
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject", "@external", "@requires"])
|
|
4771
|
+
|
|
4772
|
+
type I @key(fields: "id") @interfaceObject {
|
|
4773
|
+
id: ID!
|
|
4774
|
+
required: String @external
|
|
4775
|
+
v: String @requires(fields: "required")
|
|
4776
|
+
}
|
|
4777
|
+
`,
|
|
4778
|
+
resolvers: {
|
|
4779
|
+
I: {
|
|
4780
|
+
__resolveReference(ref: any) {
|
|
4781
|
+
return {
|
|
4782
|
+
...ref,
|
|
4783
|
+
v: `req=${ref.required}`
|
|
4784
|
+
};
|
|
4785
|
+
},
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4790
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2, s3 ]);
|
|
4791
|
+
|
|
4792
|
+
let operation = parseOp(`
|
|
4793
|
+
{
|
|
4794
|
+
ts {
|
|
4795
|
+
v
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
`, schema);
|
|
4799
|
+
|
|
4800
|
+
let queryPlan = buildPlan(operation, queryPlanner);
|
|
4801
|
+
const expectedPlan = `
|
|
4802
|
+
QueryPlan {
|
|
4803
|
+
Sequence {
|
|
4804
|
+
Fetch(service: "s1") {
|
|
4805
|
+
{
|
|
4806
|
+
ts {
|
|
4807
|
+
__typename
|
|
4808
|
+
id
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
},
|
|
4812
|
+
Flatten(path: "ts.@") {
|
|
4813
|
+
Fetch(service: "s2") {
|
|
4814
|
+
{
|
|
4815
|
+
... on I {
|
|
4816
|
+
__typename
|
|
4817
|
+
id
|
|
4818
|
+
}
|
|
4819
|
+
} =>
|
|
4820
|
+
{
|
|
4821
|
+
... on I {
|
|
4822
|
+
__typename
|
|
4823
|
+
required
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
},
|
|
4827
|
+
},
|
|
4828
|
+
Flatten(path: "ts.@") {
|
|
4829
|
+
Fetch(service: "s3") {
|
|
4830
|
+
{
|
|
4831
|
+
... on I {
|
|
4832
|
+
__typename
|
|
4833
|
+
required
|
|
4834
|
+
id
|
|
4835
|
+
}
|
|
4836
|
+
} =>
|
|
4837
|
+
{
|
|
4838
|
+
... on I {
|
|
4839
|
+
v
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
},
|
|
4843
|
+
},
|
|
4844
|
+
},
|
|
4845
|
+
}
|
|
4846
|
+
`;
|
|
4847
|
+
expect(queryPlan).toMatchInlineSnapshot(expectedPlan);
|
|
4848
|
+
|
|
4849
|
+
let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
4850
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
4851
|
+
Object {
|
|
4852
|
+
"ts": Array [
|
|
4853
|
+
Object {
|
|
4854
|
+
"v": "req=r2",
|
|
4855
|
+
},
|
|
4856
|
+
Object {
|
|
4857
|
+
"v": "req=r4",
|
|
4858
|
+
},
|
|
4859
|
+
Object {
|
|
4860
|
+
"v": "req=r1",
|
|
4861
|
+
},
|
|
4862
|
+
],
|
|
4863
|
+
}
|
|
4864
|
+
`);
|
|
4865
|
+
});
|
|
4866
|
+
|
|
4867
|
+
test('handles @requires on @interfaceObject that applies to only one of the queried implementation', async () => {
|
|
4868
|
+
// The case this test is that where the @interfaceObject in s2 has a @requires, but the query we send requests the field on which
|
|
4869
|
+
// there is this @require only for one of the implementation type, which it request another field with no require for another implementation.
|
|
4870
|
+
// And we're making sure the requirements only get queried for T1, the first type.
|
|
4871
|
+
const s1 = {
|
|
4872
|
+
name: 's1',
|
|
4873
|
+
typeDefs: gql`
|
|
4874
|
+
extend schema
|
|
4875
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key"])
|
|
4876
|
+
|
|
4877
|
+
type Query {
|
|
4878
|
+
is: [I!]!
|
|
4879
|
+
}
|
|
4880
|
+
|
|
4881
|
+
interface I @key(fields: "id") {
|
|
4882
|
+
id: ID!
|
|
4883
|
+
name: String
|
|
4884
|
+
req: Req
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
type T1 implements I @key(fields: "id") {
|
|
4888
|
+
id: ID!
|
|
4889
|
+
name: String
|
|
4890
|
+
req: Req
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4893
|
+
type T2 implements I @key(fields: "id") {
|
|
4894
|
+
id: ID!
|
|
4895
|
+
name: String
|
|
4896
|
+
req: Req
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
type Req {
|
|
4900
|
+
id: ID!
|
|
4901
|
+
}
|
|
4902
|
+
`,
|
|
4903
|
+
resolvers: {
|
|
4904
|
+
Query: {
|
|
4905
|
+
is: () => [
|
|
4906
|
+
{ __typename: 'T1', id: '2', name: 'e2', req: { id: 'r1'} },
|
|
4907
|
+
{ __typename: 'T2', id: '4', name: 'e4', req: { id: 'r2'} },
|
|
4908
|
+
{ __typename: 'T1', id: '1', name: 'e1', req: { id: 'r3'} }
|
|
4909
|
+
]
|
|
4910
|
+
},
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4913
|
+
|
|
4914
|
+
const s2 = {
|
|
4915
|
+
name: 's2',
|
|
4916
|
+
typeDefs: gql`
|
|
4917
|
+
extend schema
|
|
4918
|
+
@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@interfaceObject", "@external", "@requires"])
|
|
4919
|
+
|
|
4920
|
+
type I @key(fields: "id") @interfaceObject {
|
|
4921
|
+
id: ID!
|
|
4922
|
+
req: Req @external
|
|
4923
|
+
v: String! @requires(fields: "req { id }")
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
type Req {
|
|
4927
|
+
id: ID! @external
|
|
4928
|
+
}
|
|
4929
|
+
`,
|
|
4930
|
+
resolvers: {
|
|
4931
|
+
I: {
|
|
4932
|
+
__resolveReference(ref: any) {
|
|
4933
|
+
return {
|
|
4934
|
+
...ref,
|
|
4935
|
+
v: `req=${ref.req.id}`
|
|
4936
|
+
};
|
|
4937
|
+
},
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
|
|
4942
|
+
const { serviceMap, schema, queryPlanner} = getFederatedTestingSchema([ s1, s2 ]);
|
|
4943
|
+
|
|
4944
|
+
let operation = parseOp(`
|
|
4945
|
+
{
|
|
4946
|
+
is {
|
|
4947
|
+
... on T1 {
|
|
4948
|
+
v
|
|
4949
|
+
}
|
|
4950
|
+
... on T2 {
|
|
4951
|
+
name
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
`, schema);
|
|
4956
|
+
|
|
4957
|
+
let queryPlan = buildPlan(operation, queryPlanner);
|
|
4958
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
4959
|
+
QueryPlan {
|
|
4960
|
+
Sequence {
|
|
4961
|
+
Fetch(service: "s1") {
|
|
4962
|
+
{
|
|
4963
|
+
is {
|
|
4964
|
+
__typename
|
|
4965
|
+
... on T1 {
|
|
4966
|
+
__typename
|
|
4967
|
+
id
|
|
4968
|
+
req {
|
|
4969
|
+
id
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
... on T2 {
|
|
4973
|
+
name
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
},
|
|
4978
|
+
Flatten(path: "is.@") {
|
|
4979
|
+
Fetch(service: "s2") {
|
|
4980
|
+
{
|
|
4981
|
+
... on T1 {
|
|
4982
|
+
__typename
|
|
4983
|
+
id
|
|
4984
|
+
}
|
|
4985
|
+
... on I {
|
|
4986
|
+
__typename
|
|
4987
|
+
req {
|
|
4988
|
+
id
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4991
|
+
} =>
|
|
4992
|
+
{
|
|
4993
|
+
... on I {
|
|
4994
|
+
v
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
},
|
|
4998
|
+
},
|
|
4999
|
+
},
|
|
5000
|
+
}
|
|
5001
|
+
`);
|
|
5002
|
+
|
|
5003
|
+
let response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
5004
|
+
expect(response.errors).toBeUndefined();
|
|
5005
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
5006
|
+
Object {
|
|
5007
|
+
"is": Array [
|
|
5008
|
+
Object {
|
|
5009
|
+
"v": "req=r1",
|
|
5010
|
+
},
|
|
5011
|
+
Object {
|
|
5012
|
+
"name": "e4",
|
|
5013
|
+
},
|
|
5014
|
+
Object {
|
|
5015
|
+
"v": "req=r3",
|
|
5016
|
+
},
|
|
5017
|
+
],
|
|
5018
|
+
}
|
|
5019
|
+
`);
|
|
5020
|
+
|
|
5021
|
+
// Sanity checking that if we ask for `v` (the field with @requires), then everything still works.
|
|
5022
|
+
operation = parseOp(`
|
|
5023
|
+
{
|
|
5024
|
+
is {
|
|
5025
|
+
... on T1 {
|
|
5026
|
+
v
|
|
5027
|
+
}
|
|
5028
|
+
... on T2 {
|
|
5029
|
+
v
|
|
5030
|
+
name
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
`, schema);
|
|
5035
|
+
|
|
5036
|
+
global.console = require('console');
|
|
5037
|
+
queryPlan = buildPlan(operation, queryPlanner);
|
|
5038
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
5039
|
+
QueryPlan {
|
|
5040
|
+
Sequence {
|
|
5041
|
+
Fetch(service: "s1") {
|
|
5042
|
+
{
|
|
5043
|
+
is {
|
|
5044
|
+
__typename
|
|
5045
|
+
... on T1 {
|
|
5046
|
+
__typename
|
|
5047
|
+
id
|
|
5048
|
+
req {
|
|
5049
|
+
id
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
... on T2 {
|
|
5053
|
+
__typename
|
|
5054
|
+
id
|
|
5055
|
+
req {
|
|
5056
|
+
id
|
|
5057
|
+
}
|
|
5058
|
+
name
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
},
|
|
5063
|
+
Flatten(path: "is.@") {
|
|
5064
|
+
Fetch(service: "s2") {
|
|
5065
|
+
{
|
|
5066
|
+
... on T1 {
|
|
5067
|
+
__typename
|
|
5068
|
+
id
|
|
5069
|
+
}
|
|
5070
|
+
... on I {
|
|
5071
|
+
__typename
|
|
5072
|
+
req {
|
|
5073
|
+
id
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
... on T2 {
|
|
5077
|
+
__typename
|
|
5078
|
+
id
|
|
5079
|
+
}
|
|
5080
|
+
} =>
|
|
5081
|
+
{
|
|
5082
|
+
... on I {
|
|
5083
|
+
v
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
},
|
|
5087
|
+
},
|
|
5088
|
+
},
|
|
5089
|
+
}
|
|
5090
|
+
`);
|
|
5091
|
+
|
|
5092
|
+
response = await executePlan(queryPlan, operation, undefined, schema, serviceMap);
|
|
5093
|
+
expect(response.errors).toBeUndefined();
|
|
5094
|
+
expect(response.data).toMatchInlineSnapshot(`
|
|
5095
|
+
Object {
|
|
5096
|
+
"is": Array [
|
|
5097
|
+
Object {
|
|
5098
|
+
"v": "req=r1",
|
|
5099
|
+
},
|
|
5100
|
+
Object {
|
|
5101
|
+
"name": "e4",
|
|
5102
|
+
"v": "req=r2",
|
|
5103
|
+
},
|
|
5104
|
+
Object {
|
|
5105
|
+
"v": "req=r3",
|
|
5106
|
+
},
|
|
5107
|
+
],
|
|
5108
|
+
}
|
|
5109
|
+
`);
|
|
5110
|
+
});
|
|
4603
5111
|
});
|
|
4604
5112
|
|
|
4605
5113
|
describe('fields with conflicting types needing aliasing', () => {
|
|
@@ -88,7 +88,7 @@ it('correctly passes the context from ApolloServer to datasources', async () =>
|
|
|
88
88
|
.reply(
|
|
89
89
|
200,
|
|
90
90
|
{
|
|
91
|
-
data: { me: { username: '@
|
|
91
|
+
data: { me: { username: '@apollo-user' } },
|
|
92
92
|
},
|
|
93
93
|
replyHeaders,
|
|
94
94
|
);
|
|
@@ -107,7 +107,7 @@ it('correctly passes the context from ApolloServer to datasources', async () =>
|
|
|
107
107
|
const { data, errors } = unwrapSingleResultKind(result);
|
|
108
108
|
expect(errors).toBeUndefined();
|
|
109
109
|
expect(data).toEqual({
|
|
110
|
-
me: { username: '@
|
|
110
|
+
me: { username: '@apollo-user' },
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
|