@apollo/gateway 2.3.4 → 2.4.0-alpha.0
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/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +38 -12
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/executeQueryPlan.conditions.test.ts +1488 -0
- package/src/__tests__/executeQueryPlan.test.ts +28 -16
- package/src/__tests__/execution-utils.ts +3 -3
- package/src/__tests__/gateway/buildService.test.ts +17 -13
- package/src/__tests__/gateway/endToEnd.test.ts +91 -94
- package/src/__tests__/gateway/queryPlanCache.test.ts +18 -19
- package/src/__tests__/gateway/queryPlannerConfig.test.ts +101 -0
- package/src/__tests__/gateway/reporting.test.ts +23 -45
- package/src/__tests__/gateway/supergraphSdl.test.ts +5 -3
- package/src/__tests__/gateway/testUtils.ts +89 -0
- package/src/__tests__/integration/aliases.test.ts +5 -5
- package/src/__tests__/integration/boolean.test.ts +9 -9
- package/src/__tests__/integration/managed.test.ts +10 -9
- package/src/executeQueryPlan.ts +58 -18
- package/src/index.ts +10 -5
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import gql from 'graphql-tag';
|
|
2
|
+
import { startSubgraphsAndGateway, Services } from './testUtils'
|
|
3
|
+
|
|
4
|
+
let services: Services;
|
|
5
|
+
|
|
6
|
+
afterEach(async () => {
|
|
7
|
+
if (services) {
|
|
8
|
+
await services.stop();
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('`debug.bypassPlannerForSingleSubgraph` config', () => {
|
|
13
|
+
const subgraph = {
|
|
14
|
+
name: 'A',
|
|
15
|
+
url: 'https://A',
|
|
16
|
+
typeDefs: gql`
|
|
17
|
+
type Query {
|
|
18
|
+
a: A
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type A {
|
|
22
|
+
b: B
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type B {
|
|
26
|
+
x: Int
|
|
27
|
+
y: String
|
|
28
|
+
}
|
|
29
|
+
`,
|
|
30
|
+
resolvers: {
|
|
31
|
+
Query: {
|
|
32
|
+
a: () => ({
|
|
33
|
+
b: {
|
|
34
|
+
x: 1,
|
|
35
|
+
y: 'foo',
|
|
36
|
+
}
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const query = `
|
|
43
|
+
{
|
|
44
|
+
a {
|
|
45
|
+
b {
|
|
46
|
+
x
|
|
47
|
+
y
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const expectedResult = `
|
|
54
|
+
Object {
|
|
55
|
+
"data": Object {
|
|
56
|
+
"a": Object {
|
|
57
|
+
"b": Object {
|
|
58
|
+
"x": 1,
|
|
59
|
+
"y": "foo",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
it('is disabled by default', async () => {
|
|
67
|
+
services = await startSubgraphsAndGateway([subgraph]);
|
|
68
|
+
|
|
69
|
+
const response = await services.queryGateway(query);
|
|
70
|
+
const result = await response.json();
|
|
71
|
+
expect(result).toMatchInlineSnapshot(expectedResult);
|
|
72
|
+
|
|
73
|
+
const queryPlanner = services.gateway.__testing().queryPlanner!;
|
|
74
|
+
// If the query planner is genuinely used, we shoud have evaluated 1 plan.
|
|
75
|
+
expect(queryPlanner.lastGeneratedPlanStatistics()?.evaluatedPlanCount).toBe(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('works when enabled', async () => {
|
|
79
|
+
services = await startSubgraphsAndGateway(
|
|
80
|
+
[subgraph],
|
|
81
|
+
{
|
|
82
|
+
gatewayConfig: {
|
|
83
|
+
queryPlannerConfig: {
|
|
84
|
+
debug: {
|
|
85
|
+
bypassPlannerForSingleSubgraph: true,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const response = await services.queryGateway(query);
|
|
93
|
+
const result = await response.json();
|
|
94
|
+
expect(result).toMatchInlineSnapshot(expectedResult);
|
|
95
|
+
|
|
96
|
+
const queryPlanner = services.gateway.__testing().queryPlanner!;
|
|
97
|
+
// The `bypassPlannerForSingleSubgraph` doesn't evaluate anything. It's use is the only case where `evaluatedPlanCount` can be 0.
|
|
98
|
+
expect(queryPlanner.lastGeneratedPlanStatistics()?.evaluatedPlanCount).toBe(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { gunzipSync } from 'zlib';
|
|
2
2
|
import nock from 'nock';
|
|
3
3
|
import gql from 'graphql-tag';
|
|
4
|
-
import {
|
|
5
|
-
import { ApolloServer } from 'apollo-server';
|
|
6
|
-
import { ApolloServerPluginUsageReporting } from 'apollo-server-core';
|
|
4
|
+
import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';
|
|
7
5
|
import { execute } from '@apollo/client/link/core';
|
|
8
6
|
import { toPromise } from '@apollo/client/link/utils';
|
|
9
7
|
import { createHttpLink } from '@apollo/client/link/http';
|
|
10
8
|
import fetch from 'node-fetch';
|
|
11
|
-
import { ApolloGateway } from '../..';
|
|
12
9
|
import { Plugin, Config, Refs } from 'pretty-format';
|
|
13
10
|
import { Report, Trace } from '@apollo/usage-reporting-protobuf';
|
|
14
11
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
15
12
|
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
16
|
-
import { GraphQLSchemaModule } from '@apollo/subgraph/src/schema-helper';
|
|
17
13
|
import resolvable, { Resolvable } from '@josephg/resolvable';
|
|
14
|
+
import { startSubgraphsAndGateway, Services } from './testUtils';
|
|
18
15
|
|
|
19
16
|
// Normalize specific fields that change often (eg timestamps) to static values,
|
|
20
17
|
// to make snapshot testing viable. (If these helpers are more generally
|
|
@@ -80,17 +77,8 @@ expect.addSnapshotSerializer(
|
|
|
80
77
|
}),
|
|
81
78
|
);
|
|
82
79
|
|
|
83
|
-
async function startFederatedServer(modules: GraphQLSchemaModule[]) {
|
|
84
|
-
const schema = buildSubgraphSchema(modules);
|
|
85
|
-
const server = new ApolloServer({ schema });
|
|
86
|
-
const { url } = await server.listen({ port: 0 });
|
|
87
|
-
return { url, server };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
80
|
describe('reporting', () => {
|
|
91
|
-
let
|
|
92
|
-
let gatewayServer: ApolloServer;
|
|
93
|
-
let gatewayUrl: string;
|
|
81
|
+
let services: Services;
|
|
94
82
|
let reportPromise: Resolvable<any>;
|
|
95
83
|
|
|
96
84
|
beforeEach(async () => {
|
|
@@ -104,38 +92,27 @@ describe('reporting', () => {
|
|
|
104
92
|
return 'ok';
|
|
105
93
|
});
|
|
106
94
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
plugins: [
|
|
125
|
-
ApolloServerPluginUsageReporting({
|
|
126
|
-
sendReportsImmediately: true,
|
|
127
|
-
}),
|
|
128
|
-
],
|
|
129
|
-
});
|
|
130
|
-
({ url: gatewayUrl } = await gatewayServer.listen({ port: 0 }));
|
|
95
|
+
services = await startSubgraphsAndGateway(
|
|
96
|
+
fixtures,
|
|
97
|
+
{
|
|
98
|
+
gatewayServerConfig: {
|
|
99
|
+
apollo: {
|
|
100
|
+
key: 'service:foo:bar',
|
|
101
|
+
graphRef: 'foo@current',
|
|
102
|
+
},
|
|
103
|
+
plugins: [
|
|
104
|
+
ApolloServerPluginUsageReporting({
|
|
105
|
+
sendReportsImmediately: true,
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
);
|
|
131
111
|
});
|
|
132
112
|
|
|
133
113
|
afterEach(async () => {
|
|
134
|
-
|
|
135
|
-
await
|
|
136
|
-
}
|
|
137
|
-
if (gatewayServer) {
|
|
138
|
-
await gatewayServer.stop();
|
|
114
|
+
if (services) {
|
|
115
|
+
await services.stop();
|
|
139
116
|
}
|
|
140
117
|
|
|
141
118
|
nockAfterEach();
|
|
@@ -157,7 +134,7 @@ describe('reporting', () => {
|
|
|
157
134
|
`;
|
|
158
135
|
|
|
159
136
|
const result = await toPromise(
|
|
160
|
-
execute(createHttpLink({ uri: gatewayUrl, fetch: fetch as any }), {
|
|
137
|
+
execute(createHttpLink({ uri: services.gatewayUrl, fetch: fetch as any }), {
|
|
161
138
|
query,
|
|
162
139
|
}),
|
|
163
140
|
);
|
|
@@ -632,6 +609,7 @@ describe('reporting', () => {
|
|
|
632
609
|
],
|
|
633
610
|
},
|
|
634
611
|
},
|
|
612
|
+
"tracesPreAggregated": false,
|
|
635
613
|
}
|
|
636
614
|
`);
|
|
637
615
|
});
|
|
@@ -6,13 +6,15 @@ import {
|
|
|
6
6
|
} from '@apollo/gateway';
|
|
7
7
|
import { accounts, fixturesWithUpdate } from 'apollo-federation-integration-testsuite';
|
|
8
8
|
import { createHash } from '@apollo/utils.createhash';
|
|
9
|
-
import { ApolloServer } from 'apollo
|
|
9
|
+
import { ApolloServer } from '@apollo/server';
|
|
10
|
+
import { startStandaloneServer } from '@apollo/server/standalone';
|
|
10
11
|
import type { Logger } from '@apollo/utils.logger';
|
|
11
12
|
import { getTestingSupergraphSdl } from '../execution-utils';
|
|
12
13
|
import { mockAllServicesHealthCheckSuccess } from '../integration/nockMocks';
|
|
13
14
|
import resolvable from '@josephg/resolvable';
|
|
14
15
|
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
15
16
|
import nock from 'nock';
|
|
17
|
+
import { unwrapSingleResultKind } from '../gateway/testUtils';
|
|
16
18
|
|
|
17
19
|
async function getSupergraphSdlGatewayServer() {
|
|
18
20
|
const server = new ApolloServer({
|
|
@@ -26,7 +28,7 @@ async function getSupergraphSdlGatewayServer() {
|
|
|
26
28
|
}),
|
|
27
29
|
});
|
|
28
30
|
|
|
29
|
-
await server
|
|
31
|
+
await startStandaloneServer(server, { listen: { port: 0 } });
|
|
30
32
|
return server;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -67,7 +69,7 @@ describe('Using supergraphSdl static configuration', () => {
|
|
|
67
69
|
query: '{ me { username } }',
|
|
68
70
|
});
|
|
69
71
|
|
|
70
|
-
expect(result.data).toMatchInlineSnapshot(`
|
|
72
|
+
expect(unwrapSingleResultKind(result).data).toMatchInlineSnapshot(`
|
|
71
73
|
Object {
|
|
72
74
|
"me": Object {
|
|
73
75
|
"username": "@jbaxleyiii",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
2
|
+
import {
|
|
3
|
+
ApolloServer,
|
|
4
|
+
ApolloServerOptionsWithGateway,
|
|
5
|
+
BaseContext,
|
|
6
|
+
GraphQLResponse,
|
|
7
|
+
} from '@apollo/server';
|
|
8
|
+
import { startStandaloneServer } from '@apollo/server/standalone';
|
|
9
|
+
import { ApolloServerPluginInlineTrace } from '@apollo/server/plugin/inlineTrace';
|
|
10
|
+
import { GraphQLSchemaModule } from '@apollo/subgraph/src/schema-helper';
|
|
11
|
+
import { ApolloGateway, GatewayConfig } from '../..';
|
|
12
|
+
import { ServiceDefinition } from '@apollo/federation-internals';
|
|
13
|
+
import fetch, { Response } from 'node-fetch';
|
|
14
|
+
import { assert } from '@apollo/federation-internals';
|
|
15
|
+
|
|
16
|
+
export class Services {
|
|
17
|
+
constructor(
|
|
18
|
+
readonly subgraphServers: ApolloServer[],
|
|
19
|
+
readonly gateway: ApolloGateway,
|
|
20
|
+
readonly gatewayServer: ApolloServer,
|
|
21
|
+
readonly gatewayUrl: string,
|
|
22
|
+
) {
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async queryGateway(query: string): Promise<Response> {
|
|
26
|
+
return fetch(this.gatewayUrl, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({ query }),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async stop() {
|
|
36
|
+
for (const server of this.subgraphServers) {
|
|
37
|
+
await server.stop();
|
|
38
|
+
}
|
|
39
|
+
await this.gatewayServer.stop();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function startFederatedServer(modules: GraphQLSchemaModule[]) {
|
|
44
|
+
const schema = buildSubgraphSchema(modules);
|
|
45
|
+
const server = new ApolloServer({
|
|
46
|
+
schema,
|
|
47
|
+
// Manually installing the inline trace plugin means it doesn't log a message.
|
|
48
|
+
plugins: [ApolloServerPluginInlineTrace()],
|
|
49
|
+
});
|
|
50
|
+
const { url } = await startStandaloneServer(server, { listen: { port: 0 } });
|
|
51
|
+
return { url, server };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function startSubgraphsAndGateway(
|
|
55
|
+
servicesDefs: ServiceDefinition[],
|
|
56
|
+
config?: {
|
|
57
|
+
gatewayConfig?: GatewayConfig;
|
|
58
|
+
gatewayServerConfig?: Partial<ApolloServerOptionsWithGateway<BaseContext>>;
|
|
59
|
+
},
|
|
60
|
+
): Promise<Services> {
|
|
61
|
+
const backendServers = [];
|
|
62
|
+
const serviceList = [];
|
|
63
|
+
for (const serviceDef of servicesDefs) {
|
|
64
|
+
const { server, url } = await startFederatedServer([serviceDef]);
|
|
65
|
+
backendServers.push(server);
|
|
66
|
+
serviceList.push({ name: serviceDef.name, url });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const gateway = new ApolloGateway({
|
|
70
|
+
serviceList,
|
|
71
|
+
...config?.gatewayConfig,
|
|
72
|
+
});
|
|
73
|
+
const gatewayServer = new ApolloServer({
|
|
74
|
+
gateway,
|
|
75
|
+
...config?.gatewayServerConfig,
|
|
76
|
+
});
|
|
77
|
+
const { url: gatewayUrl } = await startStandaloneServer(gatewayServer, {
|
|
78
|
+
listen: { port: 0 },
|
|
79
|
+
});
|
|
80
|
+
return new Services(backendServers, gateway, gatewayServer, gatewayUrl);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function unwrapSingleResultKind(response: GraphQLResponse) {
|
|
84
|
+
assert(
|
|
85
|
+
response.body.kind === 'single',
|
|
86
|
+
`Expected single result, got ${response.body.kind}`,
|
|
87
|
+
);
|
|
88
|
+
return response.body.singleResult;
|
|
89
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execute } from '../execution-utils';
|
|
2
|
-
import {
|
|
2
|
+
import { ApolloServer } from '@apollo/server';
|
|
3
3
|
import { buildSubgraphSchema } from '@apollo/subgraph';
|
|
4
4
|
import { LocalGraphQLDataSource } from '../../datasources/LocalGraphQLDataSource';
|
|
5
5
|
import { ApolloGateway } from '../../';
|
|
6
6
|
import { fixtures } from 'apollo-federation-integration-testsuite';
|
|
7
|
+
import { unwrapSingleResultKind } from '../gateway/testUtils';
|
|
7
8
|
|
|
8
9
|
it('supports simple aliases', async () => {
|
|
9
10
|
const query = `#graphql
|
|
@@ -155,9 +156,8 @@ it('supports aliases when using ApolloServer', async () => {
|
|
|
155
156
|
},
|
|
156
157
|
});
|
|
157
158
|
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const server = new ApolloServer({ schema, executor });
|
|
159
|
+
const server = new ApolloServer({ gateway });
|
|
160
|
+
await server.start();
|
|
161
161
|
|
|
162
162
|
const upc = '1';
|
|
163
163
|
|
|
@@ -172,7 +172,7 @@ it('supports aliases when using ApolloServer', async () => {
|
|
|
172
172
|
variables: { upc },
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
expect(result.data).toEqual({
|
|
175
|
+
expect(unwrapSingleResultKind(result).data).toEqual({
|
|
176
176
|
product: {
|
|
177
177
|
title: 'Table',
|
|
178
178
|
},
|
|
@@ -4,8 +4,6 @@ import { astSerializer, queryPlanSerializer } from 'apollo-federation-integratio
|
|
|
4
4
|
expect.addSnapshotSerializer(astSerializer);
|
|
5
5
|
expect.addSnapshotSerializer(queryPlanSerializer);
|
|
6
6
|
|
|
7
|
-
// TODO: right now the query planner doesn't prune known skip and include points
|
|
8
|
-
// eventually we want to do this to prevent downstream fetches that aren't needed
|
|
9
7
|
describe('@skip', () => {
|
|
10
8
|
it('supports @skip when a boolean condition is met', async () => {
|
|
11
9
|
const query = `#graphql
|
|
@@ -35,8 +33,8 @@ describe('@skip', () => {
|
|
|
35
33
|
],
|
|
36
34
|
});
|
|
37
35
|
|
|
38
|
-
expect(queryPlan).toCallService('accounts');
|
|
39
36
|
expect(queryPlan).toCallService('reviews');
|
|
37
|
+
expect(queryPlan).not.toCallService('accounts');
|
|
40
38
|
});
|
|
41
39
|
|
|
42
40
|
it('supports @skip when a boolean condition is met (variable driven)', async () => {
|
|
@@ -122,7 +120,7 @@ describe('@skip', () => {
|
|
|
122
120
|
`;
|
|
123
121
|
|
|
124
122
|
const skip = false;
|
|
125
|
-
const { data, queryPlan } = await execute({
|
|
123
|
+
const { data, queryPlan, operation } = await execute({
|
|
126
124
|
query,
|
|
127
125
|
variables: { skip },
|
|
128
126
|
});
|
|
@@ -137,8 +135,9 @@ describe('@skip', () => {
|
|
|
137
135
|
],
|
|
138
136
|
});
|
|
139
137
|
|
|
140
|
-
|
|
141
|
-
expect(queryPlan).toCallService('reviews');
|
|
138
|
+
const variables = { definitions: operation.variableDefinitions, values: {skip} };
|
|
139
|
+
expect(queryPlan).toCallService('reviews', variables);
|
|
140
|
+
expect(queryPlan).toCallService('accounts', variables);
|
|
142
141
|
});
|
|
143
142
|
});
|
|
144
143
|
|
|
@@ -258,7 +257,7 @@ describe('@include', () => {
|
|
|
258
257
|
`;
|
|
259
258
|
|
|
260
259
|
const include = true;
|
|
261
|
-
const { data, queryPlan } = await execute({
|
|
260
|
+
const { data, queryPlan, operation } = await execute({
|
|
262
261
|
query,
|
|
263
262
|
variables: { include },
|
|
264
263
|
});
|
|
@@ -273,7 +272,8 @@ describe('@include', () => {
|
|
|
273
272
|
],
|
|
274
273
|
});
|
|
275
274
|
|
|
276
|
-
|
|
277
|
-
expect(queryPlan).toCallService('
|
|
275
|
+
const variables = { definitions: operation.variableDefinitions, values: {include} };
|
|
276
|
+
expect(queryPlan).toCallService('accounts', variables);
|
|
277
|
+
expect(queryPlan).toCallService('reviews', variables);
|
|
278
278
|
});
|
|
279
279
|
});
|
|
@@ -2,8 +2,9 @@ import mockedEnv from 'mocked-env';
|
|
|
2
2
|
import fetcher from 'make-fetch-happen';
|
|
3
3
|
|
|
4
4
|
import { ApolloGateway, UplinkSupergraphManager } from '@apollo/gateway';
|
|
5
|
-
import { ApolloServer } from 'apollo
|
|
6
|
-
import {
|
|
5
|
+
import { ApolloServer } from '@apollo/server';
|
|
6
|
+
import { startStandaloneServer } from '@apollo/server/standalone';
|
|
7
|
+
import { ApolloServerPluginUsageReportingDisabled } from '@apollo/server/plugin/disabled';
|
|
7
8
|
import type { FetcherRequestInit } from '@apollo/utils.fetcher';
|
|
8
9
|
|
|
9
10
|
import { nockAfterEach, nockBeforeEach } from '../nockAssertions';
|
|
@@ -62,7 +63,7 @@ describe('minimal gateway', () => {
|
|
|
62
63
|
gateway,
|
|
63
64
|
plugins: [ApolloServerPluginUsageReportingDisabled()],
|
|
64
65
|
});
|
|
65
|
-
await server
|
|
66
|
+
await startStandaloneServer(server, { listen: { port: 0 } });
|
|
66
67
|
expect(gateway.supergraphManager).toBeInstanceOf(UplinkSupergraphManager);
|
|
67
68
|
});
|
|
68
69
|
|
|
@@ -80,7 +81,7 @@ describe('minimal gateway', () => {
|
|
|
80
81
|
gateway,
|
|
81
82
|
plugins: [ApolloServerPluginUsageReportingDisabled()],
|
|
82
83
|
});
|
|
83
|
-
await server
|
|
84
|
+
await startStandaloneServer(server, { listen: { port: 0 } });
|
|
84
85
|
expect(gateway.supergraphManager).toBeInstanceOf(UplinkSupergraphManager);
|
|
85
86
|
const uplinkManager = gateway.supergraphManager as UplinkSupergraphManager;
|
|
86
87
|
expect(uplinkManager.uplinkEndpoints).toEqual([uplinkEndpoint]);
|
|
@@ -100,7 +101,7 @@ describe('minimal gateway', () => {
|
|
|
100
101
|
gateway,
|
|
101
102
|
plugins: [ApolloServerPluginUsageReportingDisabled()],
|
|
102
103
|
});
|
|
103
|
-
await server
|
|
104
|
+
await startStandaloneServer(server, { listen: { port: 0 } });
|
|
104
105
|
expect(gateway.supergraphManager).toBeInstanceOf(UplinkSupergraphManager);
|
|
105
106
|
const uplinkManager = gateway.supergraphManager as UplinkSupergraphManager;
|
|
106
107
|
expect(uplinkManager.uplinkEndpoints).toEqual([
|
|
@@ -127,7 +128,7 @@ describe('minimal gateway', () => {
|
|
|
127
128
|
gateway,
|
|
128
129
|
plugins: [ApolloServerPluginUsageReportingDisabled()],
|
|
129
130
|
});
|
|
130
|
-
await server
|
|
131
|
+
await startStandaloneServer(server, { listen: { port: 0 } });
|
|
131
132
|
|
|
132
133
|
expect(calls).toEqual(1);
|
|
133
134
|
});
|
|
@@ -180,7 +181,7 @@ describe('Managed gateway with explicit UplinkSupergraphManager', () => {
|
|
|
180
181
|
|
|
181
182
|
const supergraphSchema = getTestingSupergraphSdl();
|
|
182
183
|
let hasFired;
|
|
183
|
-
|
|
184
|
+
const uplinkManager = new UplinkSupergraphManager({
|
|
184
185
|
apiKey,
|
|
185
186
|
graphRef,
|
|
186
187
|
logger,
|
|
@@ -246,7 +247,7 @@ describe('Managed gateway with explicit UplinkSupergraphManager', () => {
|
|
|
246
247
|
.reply(500);
|
|
247
248
|
|
|
248
249
|
let hasFired;
|
|
249
|
-
|
|
250
|
+
const uplinkManager = new UplinkSupergraphManager({
|
|
250
251
|
apiKey,
|
|
251
252
|
graphRef,
|
|
252
253
|
logger,
|
|
@@ -285,7 +286,7 @@ describe('Managed gateway with explicit UplinkSupergraphManager', () => {
|
|
|
285
286
|
.reply(500);
|
|
286
287
|
|
|
287
288
|
let hasFired;
|
|
288
|
-
|
|
289
|
+
const uplinkManager = new UplinkSupergraphManager({
|
|
289
290
|
apiKey,
|
|
290
291
|
graphRef,
|
|
291
292
|
logger,
|
package/src/executeQueryPlan.ts
CHANGED
|
@@ -28,12 +28,13 @@ import {
|
|
|
28
28
|
getResponseName,
|
|
29
29
|
FetchDataInputRewrite,
|
|
30
30
|
FetchDataOutputRewrite,
|
|
31
|
+
evaluateCondition,
|
|
31
32
|
} from '@apollo/query-planner';
|
|
32
33
|
import { deepMerge } from './utilities/deepMerge';
|
|
33
34
|
import { isNotNullOrUndefined } from './utilities/array';
|
|
34
35
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
35
36
|
import { OpenTelemetrySpanNames, tracer } from "./utilities/opentelemetry";
|
|
36
|
-
import { assert, defaultRootName, errorCodeDef, ERRORS, isDefined, operationFromDocument, Schema } from '@apollo/federation-internals';
|
|
37
|
+
import { assert, defaultRootName, errorCodeDef, ERRORS, isDefined, Operation, operationFromDocument, Schema } from '@apollo/federation-internals';
|
|
37
38
|
import { GatewayGraphQLRequestContext, GatewayExecutionResult } from '@apollo/server-gateway-interface';
|
|
38
39
|
import { computeResponse } from './resultShaping';
|
|
39
40
|
|
|
@@ -64,6 +65,7 @@ type ResultCursor = {
|
|
|
64
65
|
interface ExecutionContext {
|
|
65
66
|
queryPlan: QueryPlan;
|
|
66
67
|
operationContext: OperationContext;
|
|
68
|
+
operation: Operation,
|
|
67
69
|
serviceMap: ServiceMap;
|
|
68
70
|
requestContext: GatewayGraphQLRequestContext;
|
|
69
71
|
supergraphSchema: GraphQLSchema;
|
|
@@ -114,9 +116,39 @@ export async function executeQueryPlan(
|
|
|
114
116
|
try {
|
|
115
117
|
const errors: GraphQLError[] = [];
|
|
116
118
|
|
|
119
|
+
let operation: Operation;
|
|
120
|
+
try {
|
|
121
|
+
operation = operationFromDocument(
|
|
122
|
+
apiSchema,
|
|
123
|
+
{
|
|
124
|
+
kind: Kind.DOCUMENT,
|
|
125
|
+
definitions: [
|
|
126
|
+
operationContext.operation,
|
|
127
|
+
...Object.values(operationContext.fragments),
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
validate: false,
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
// We shouldn't really have errors as the operation should already have been validated, but if something still
|
|
136
|
+
// happens, we should report it properly (plus, some of our tests call this method directly and blow up if we don't
|
|
137
|
+
// handle this correctly).
|
|
138
|
+
// TODO: we are doing some duplicate work by building both `OperationContext` and this `Operation`. Ideally we
|
|
139
|
+
// would remove `OperationContext`, pass the `Operation` directly to this method, and only use that. This would change
|
|
140
|
+
// the signature of this method though and it is exported so ... maybe later ?
|
|
141
|
+
//
|
|
142
|
+
if (err instanceof GraphQLError) {
|
|
143
|
+
return { errors: [err] };
|
|
144
|
+
}
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
|
|
117
148
|
const context: ExecutionContext = {
|
|
118
149
|
queryPlan,
|
|
119
150
|
operationContext,
|
|
151
|
+
operation,
|
|
120
152
|
serviceMap,
|
|
121
153
|
requestContext,
|
|
122
154
|
supergraphSchema,
|
|
@@ -129,6 +161,10 @@ export async function executeQueryPlan(
|
|
|
129
161
|
requestContext.metrics && requestContext.metrics.captureTraces
|
|
130
162
|
);
|
|
131
163
|
|
|
164
|
+
if (queryPlan.node?.kind === 'Subscription') {
|
|
165
|
+
throw new Error('Execution of subscriptions not supported by gateway');
|
|
166
|
+
}
|
|
167
|
+
|
|
132
168
|
if (queryPlan.node) {
|
|
133
169
|
const traceNode = await executeNode(
|
|
134
170
|
context,
|
|
@@ -148,20 +184,6 @@ export async function executeQueryPlan(
|
|
|
148
184
|
const result = await tracer.startActiveSpan(OpenTelemetrySpanNames.POST_PROCESSING, async (span) => {
|
|
149
185
|
let data;
|
|
150
186
|
try {
|
|
151
|
-
const operation = operationFromDocument(
|
|
152
|
-
apiSchema,
|
|
153
|
-
{
|
|
154
|
-
kind: Kind.DOCUMENT,
|
|
155
|
-
definitions: [
|
|
156
|
-
operationContext.operation,
|
|
157
|
-
...Object.values(operationContext.fragments),
|
|
158
|
-
],
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
validate: false,
|
|
162
|
-
}
|
|
163
|
-
);
|
|
164
|
-
|
|
165
187
|
let postProcessingErrors: GraphQLError[];
|
|
166
188
|
({ data, errors: postProcessingErrors } = computeResponse({
|
|
167
189
|
operation,
|
|
@@ -326,12 +348,30 @@ async function executeNode(
|
|
|
326
348
|
}
|
|
327
349
|
return new Trace.QueryPlanNode({ fetch: traceNode });
|
|
328
350
|
}
|
|
351
|
+
case 'Condition': {
|
|
352
|
+
const condition = evaluateCondition(node, context.operation.variableDefinitions, context.requestContext.request.variables);
|
|
353
|
+
const pickedBranch = condition ? node.ifClause : node.elseClause;
|
|
354
|
+
let branchTraceNode: Trace.QueryPlanNode | undefined = undefined;
|
|
355
|
+
if (pickedBranch) {
|
|
356
|
+
branchTraceNode = await executeNode(
|
|
357
|
+
context,
|
|
358
|
+
pickedBranch,
|
|
359
|
+
currentCursor,
|
|
360
|
+
captureTraces
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return new Trace.QueryPlanNode({
|
|
365
|
+
condition: new Trace.QueryPlanNode.ConditionNode({
|
|
366
|
+
condition: node.condition,
|
|
367
|
+
ifClause: condition ? branchTraceNode : undefined,
|
|
368
|
+
elseClause: condition ? undefined : branchTraceNode,
|
|
369
|
+
}),
|
|
370
|
+
});
|
|
371
|
+
}
|
|
329
372
|
case 'Defer': {
|
|
330
373
|
assert(false, `@defer support is not available in the gateway`);
|
|
331
374
|
}
|
|
332
|
-
case 'Condition': {
|
|
333
|
-
assert(false, `Condition nodes are not available in the gateway`);
|
|
334
|
-
}
|
|
335
375
|
}
|
|
336
376
|
}
|
|
337
377
|
|