@apollo/gateway 0.300.0-alpha.2 → 2.0.0-alpha.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.
Files changed (198) hide show
  1. package/LICENSE +95 -0
  2. package/README.md +1 -1
  3. package/dist/__generated__/graphqlTypes.d.ts +130 -0
  4. package/dist/__generated__/graphqlTypes.d.ts.map +1 -0
  5. package/dist/__generated__/graphqlTypes.js +25 -0
  6. package/dist/__generated__/graphqlTypes.js.map +1 -0
  7. package/dist/config.d.ts +104 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +47 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/datasources/LocalGraphQLDataSource.d.ts +3 -3
  12. package/dist/datasources/LocalGraphQLDataSource.d.ts.map +1 -1
  13. package/dist/datasources/LocalGraphQLDataSource.js +5 -5
  14. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  15. package/dist/datasources/RemoteGraphQLDataSource.d.ts +6 -4
  16. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  17. package/dist/datasources/RemoteGraphQLDataSource.js +60 -17
  18. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  19. package/dist/datasources/index.d.ts +1 -1
  20. package/dist/datasources/index.d.ts.map +1 -1
  21. package/dist/datasources/index.js +1 -0
  22. package/dist/datasources/index.js.map +1 -1
  23. package/dist/datasources/parseCacheControlHeader.d.ts +2 -0
  24. package/dist/datasources/parseCacheControlHeader.d.ts.map +1 -0
  25. package/dist/datasources/parseCacheControlHeader.js +16 -0
  26. package/dist/datasources/parseCacheControlHeader.js.map +1 -0
  27. package/dist/datasources/types.d.ts +16 -1
  28. package/dist/datasources/types.d.ts.map +1 -1
  29. package/dist/datasources/types.js +7 -0
  30. package/dist/datasources/types.js.map +1 -1
  31. package/dist/executeQueryPlan.d.ts +2 -1
  32. package/dist/executeQueryPlan.d.ts.map +1 -1
  33. package/dist/executeQueryPlan.js +199 -112
  34. package/dist/executeQueryPlan.js.map +1 -1
  35. package/dist/index.d.ts +62 -80
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +543 -234
  38. package/dist/index.js.map +1 -1
  39. package/dist/loadServicesFromRemoteEndpoint.d.ts +9 -9
  40. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
  41. package/dist/loadServicesFromRemoteEndpoint.js +13 -8
  42. package/dist/loadServicesFromRemoteEndpoint.js.map +1 -1
  43. package/dist/loadSupergraphSdlFromStorage.d.ts +13 -0
  44. package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  45. package/dist/loadSupergraphSdlFromStorage.js +101 -0
  46. package/dist/loadSupergraphSdlFromStorage.js.map +1 -0
  47. package/dist/operationContext.d.ts +17 -0
  48. package/dist/operationContext.d.ts.map +1 -0
  49. package/dist/operationContext.js +42 -0
  50. package/dist/operationContext.js.map +1 -0
  51. package/dist/outOfBandReporter.d.ts +15 -0
  52. package/dist/outOfBandReporter.d.ts.map +1 -0
  53. package/dist/outOfBandReporter.js +88 -0
  54. package/dist/outOfBandReporter.js.map +1 -0
  55. package/dist/utilities/array.d.ts +1 -2
  56. package/dist/utilities/array.d.ts.map +1 -1
  57. package/dist/utilities/array.js +7 -14
  58. package/dist/utilities/array.js.map +1 -1
  59. package/dist/utilities/assert.d.ts +2 -0
  60. package/dist/utilities/assert.d.ts.map +1 -0
  61. package/dist/utilities/assert.js +10 -0
  62. package/dist/utilities/assert.js.map +1 -0
  63. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts +3 -0
  64. package/dist/utilities/cleanErrorOfInaccessibleNames.d.ts.map +1 -0
  65. package/dist/utilities/cleanErrorOfInaccessibleNames.js +27 -0
  66. package/dist/utilities/cleanErrorOfInaccessibleNames.js.map +1 -0
  67. package/dist/utilities/deepMerge.js +2 -2
  68. package/dist/utilities/deepMerge.js.map +1 -1
  69. package/dist/utilities/graphql.d.ts +1 -4
  70. package/dist/utilities/graphql.d.ts.map +1 -1
  71. package/dist/utilities/graphql.js +3 -36
  72. package/dist/utilities/graphql.js.map +1 -1
  73. package/dist/utilities/opentelemetry.d.ts +10 -0
  74. package/dist/utilities/opentelemetry.d.ts.map +1 -0
  75. package/dist/utilities/opentelemetry.js +19 -0
  76. package/dist/utilities/opentelemetry.js.map +1 -0
  77. package/package.json +30 -21
  78. package/src/__generated__/graphqlTypes.ts +140 -0
  79. package/src/__mocks__/apollo-server-env.ts +56 -0
  80. package/src/__mocks__/make-fetch-happen-fetcher.ts +55 -0
  81. package/src/__mocks__/tsconfig.json +7 -0
  82. package/src/__tests__/build-query-plan.feature +40 -311
  83. package/src/__tests__/buildQueryPlan.test.ts +246 -426
  84. package/src/__tests__/executeQueryPlan.test.ts +1691 -194
  85. package/src/__tests__/execution-utils.ts +33 -26
  86. package/src/__tests__/gateway/__snapshots__/opentelemetry.test.ts.snap +195 -0
  87. package/src/__tests__/gateway/buildService.test.ts +16 -19
  88. package/src/__tests__/gateway/composedSdl.test.ts +44 -0
  89. package/src/__tests__/gateway/endToEnd.test.ts +166 -0
  90. package/src/__tests__/gateway/executor.test.ts +49 -43
  91. package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -29
  92. package/src/__tests__/gateway/opentelemetry.test.ts +123 -0
  93. package/src/__tests__/gateway/queryPlanCache.test.ts +19 -20
  94. package/src/__tests__/gateway/reporting.test.ts +76 -55
  95. package/src/__tests__/integration/abstract-types.test.ts +1086 -22
  96. package/src/__tests__/integration/aliases.test.ts +5 -6
  97. package/src/__tests__/integration/boolean.test.ts +40 -38
  98. package/src/__tests__/integration/complex-key.test.ts +41 -56
  99. package/src/__tests__/integration/configuration.test.ts +321 -0
  100. package/src/__tests__/integration/custom-directives.test.ts +61 -46
  101. package/src/__tests__/integration/fragments.test.ts +8 -2
  102. package/src/__tests__/integration/list-key.test.ts +2 -2
  103. package/src/__tests__/integration/logger.test.ts +2 -2
  104. package/src/__tests__/integration/multiple-key.test.ts +11 -12
  105. package/src/__tests__/integration/mutations.test.ts +8 -5
  106. package/src/__tests__/integration/networkRequests.test.ts +447 -289
  107. package/src/__tests__/integration/nockMocks.ts +95 -66
  108. package/src/__tests__/integration/provides.test.ts +9 -6
  109. package/src/__tests__/integration/requires.test.ts +17 -15
  110. package/src/__tests__/integration/scope.test.ts +557 -0
  111. package/src/__tests__/integration/unions.test.ts +1 -1
  112. package/src/__tests__/integration/value-types.test.ts +35 -32
  113. package/src/__tests__/integration/variables.test.ts +8 -2
  114. package/src/__tests__/loadServicesFromRemoteEndpoint.test.ts +6 -2
  115. package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +694 -0
  116. package/src/__tests__/queryPlanCucumber.test.ts +11 -61
  117. package/src/__tests__/testSetup.ts +1 -4
  118. package/src/__tests__/tsconfig.json +2 -1
  119. package/src/config.ts +225 -0
  120. package/src/core/__tests__/core.test.ts +412 -0
  121. package/src/datasources/LocalGraphQLDataSource.ts +9 -10
  122. package/src/datasources/RemoteGraphQLDataSource.ts +117 -43
  123. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +11 -4
  124. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +148 -79
  125. package/src/datasources/__tests__/tsconfig.json +4 -2
  126. package/src/datasources/index.ts +1 -1
  127. package/src/datasources/parseCacheControlHeader.ts +43 -0
  128. package/src/datasources/types.ts +47 -2
  129. package/src/executeQueryPlan.ts +264 -153
  130. package/src/index.ts +925 -480
  131. package/src/loadServicesFromRemoteEndpoint.ts +24 -17
  132. package/src/loadSupergraphSdlFromStorage.ts +140 -0
  133. package/src/make-fetch-happen.d.ts +2 -2
  134. package/src/operationContext.ts +70 -0
  135. package/src/outOfBandReporter.ts +128 -0
  136. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +104 -0
  137. package/src/utilities/__tests__/tsconfig.json +8 -0
  138. package/src/utilities/array.ts +6 -28
  139. package/src/utilities/assert.ts +14 -0
  140. package/src/utilities/cleanErrorOfInaccessibleNames.ts +29 -0
  141. package/src/utilities/graphql.ts +0 -64
  142. package/src/utilities/opentelemetry.ts +13 -0
  143. package/CHANGELOG.md +0 -226
  144. package/LICENSE.md +0 -20
  145. package/dist/FieldSet.d.ts +0 -18
  146. package/dist/FieldSet.d.ts.map +0 -1
  147. package/dist/FieldSet.js +0 -96
  148. package/dist/FieldSet.js.map +0 -1
  149. package/dist/QueryPlan.d.ts +0 -41
  150. package/dist/QueryPlan.d.ts.map +0 -1
  151. package/dist/QueryPlan.js +0 -15
  152. package/dist/QueryPlan.js.map +0 -1
  153. package/dist/buildQueryPlan.d.ts +0 -44
  154. package/dist/buildQueryPlan.d.ts.map +0 -1
  155. package/dist/buildQueryPlan.js +0 -670
  156. package/dist/buildQueryPlan.js.map +0 -1
  157. package/dist/loadServicesFromStorage.d.ts +0 -21
  158. package/dist/loadServicesFromStorage.d.ts.map +0 -1
  159. package/dist/loadServicesFromStorage.js +0 -64
  160. package/dist/loadServicesFromStorage.js.map +0 -1
  161. package/dist/snapshotSerializers/astSerializer.d.ts +0 -3
  162. package/dist/snapshotSerializers/astSerializer.d.ts.map +0 -1
  163. package/dist/snapshotSerializers/astSerializer.js +0 -14
  164. package/dist/snapshotSerializers/astSerializer.js.map +0 -1
  165. package/dist/snapshotSerializers/index.d.ts +0 -13
  166. package/dist/snapshotSerializers/index.d.ts.map +0 -1
  167. package/dist/snapshotSerializers/index.js +0 -15
  168. package/dist/snapshotSerializers/index.js.map +0 -1
  169. package/dist/snapshotSerializers/queryPlanSerializer.d.ts +0 -3
  170. package/dist/snapshotSerializers/queryPlanSerializer.d.ts.map +0 -1
  171. package/dist/snapshotSerializers/queryPlanSerializer.js +0 -78
  172. package/dist/snapshotSerializers/queryPlanSerializer.js.map +0 -1
  173. package/dist/snapshotSerializers/selectionSetSerializer.d.ts +0 -3
  174. package/dist/snapshotSerializers/selectionSetSerializer.d.ts.map +0 -1
  175. package/dist/snapshotSerializers/selectionSetSerializer.js +0 -12
  176. package/dist/snapshotSerializers/selectionSetSerializer.js.map +0 -1
  177. package/dist/snapshotSerializers/typeSerializer.d.ts +0 -3
  178. package/dist/snapshotSerializers/typeSerializer.d.ts.map +0 -1
  179. package/dist/snapshotSerializers/typeSerializer.js +0 -12
  180. package/dist/snapshotSerializers/typeSerializer.js.map +0 -1
  181. package/dist/utilities/MultiMap.d.ts +0 -4
  182. package/dist/utilities/MultiMap.d.ts.map +0 -1
  183. package/dist/utilities/MultiMap.js +0 -17
  184. package/dist/utilities/MultiMap.js.map +0 -1
  185. package/src/FieldSet.ts +0 -169
  186. package/src/QueryPlan.ts +0 -57
  187. package/src/__tests__/matchers/toCallService.ts +0 -105
  188. package/src/__tests__/matchers/toHaveBeenCalledBefore.ts +0 -40
  189. package/src/__tests__/matchers/toHaveFetched.ts +0 -81
  190. package/src/__tests__/matchers/toMatchAST.ts +0 -64
  191. package/src/buildQueryPlan.ts +0 -1190
  192. package/src/loadServicesFromStorage.ts +0 -170
  193. package/src/snapshotSerializers/astSerializer.ts +0 -21
  194. package/src/snapshotSerializers/index.ts +0 -21
  195. package/src/snapshotSerializers/queryPlanSerializer.ts +0 -144
  196. package/src/snapshotSerializers/selectionSetSerializer.ts +0 -13
  197. package/src/snapshotSerializers/typeSerializer.ts +0 -11
  198. package/src/utilities/MultiMap.ts +0 -11
@@ -1,23 +1,26 @@
1
1
  import { GraphQLRequest } from 'apollo-server-types';
2
2
  import { parse } from 'graphql';
3
3
  import { Headers, HeadersInit } from 'node-fetch';
4
- import { GraphQLDataSource } from './datasources/types';
5
- import { Experimental_UpdateServiceDefinitions, SERVICE_DEFINITION_QUERY } from './';
6
- import { ServiceDefinition } from '@apollo/federation';
4
+ import { GraphQLDataSource, GraphQLDataSourceRequestKind } from './datasources/types';
5
+ import { SERVICE_DEFINITION_QUERY } from './';
6
+ import { CompositionUpdate, ServiceEndpointDefinition } from './config';
7
+ import { ServiceDefinition } from '@apollo/federation-internals';
8
+
9
+ type Service = ServiceEndpointDefinition & {
10
+ dataSource: GraphQLDataSource;
11
+ };
7
12
 
8
13
  export async function getServiceDefinitionsFromRemoteEndpoint({
9
14
  serviceList,
10
- headers = {},
15
+ getServiceIntrospectionHeaders,
11
16
  serviceSdlCache,
12
17
  }: {
13
- serviceList: {
14
- name: string;
15
- url?: string;
16
- dataSource: GraphQLDataSource;
17
- }[];
18
- headers?: HeadersInit;
18
+ serviceList: Service[];
19
+ getServiceIntrospectionHeaders: (
20
+ service: ServiceEndpointDefinition,
21
+ ) => Promise<HeadersInit | undefined>;
19
22
  serviceSdlCache: Map<string, string>;
20
- }): ReturnType<Experimental_UpdateServiceDefinitions> {
23
+ }): Promise<CompositionUpdate> {
21
24
  if (!serviceList || !serviceList.length) {
22
25
  throw new Error(
23
26
  'Tried to load services from remote endpoints but none provided',
@@ -26,7 +29,7 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
26
29
 
27
30
  let isNewSchema = false;
28
31
  // for each service, fetch its introspection schema
29
- const promiseOfServiceList = serviceList.map(({ name, url, dataSource }) => {
32
+ const promiseOfServiceList = serviceList.map(async ({ name, url, dataSource }) => {
30
33
  if (!url) {
31
34
  throw new Error(
32
35
  `Tried to load schema for '${name}' but no 'url' was specified.`);
@@ -37,12 +40,16 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
37
40
  http: {
38
41
  url,
39
42
  method: 'POST',
40
- headers: new Headers(headers),
43
+ headers: new Headers(await getServiceIntrospectionHeaders({ name, url })),
41
44
  },
42
45
  };
43
46
 
44
47
  return dataSource
45
- .process({ request, context: {} })
48
+ .process({
49
+ kind: GraphQLDataSourceRequestKind.LOADING_SCHEMA,
50
+ request,
51
+ context: {},
52
+ })
46
53
  .then(({ data, errors }): ServiceDefinition => {
47
54
  if (data && !errors) {
48
55
  const typeDefs = data._service.sdl as string;
@@ -60,12 +67,12 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
60
67
  };
61
68
  }
62
69
 
63
- throw new Error(errors?.map(e => e.message).join("\n"));
70
+ throw new Error(errors?.map((e) => e.message).join('\n'));
64
71
  })
65
- .catch(err => {
72
+ .catch((err) => {
66
73
  const errorMessage =
67
74
  `Couldn't load service definitions for "${name}" at ${url}` +
68
- (err && err.message ? ": " + err.message || err : "");
75
+ (err && err.message ? ': ' + err.message || err : '');
69
76
 
70
77
  throw new Error(errorMessage);
71
78
  });
@@ -0,0 +1,140 @@
1
+ import { fetch, Response, Request } from 'apollo-server-env';
2
+ import { GraphQLError } from 'graphql';
3
+ import { OutOfBandReporter } from './outOfBandReporter';
4
+ import { SupergraphSdlQuery } from './__generated__/graphqlTypes';
5
+
6
+ // Magic /* GraphQL */ comment below is for codegen, do not remove
7
+ export const SUPERGRAPH_SDL_QUERY = /* GraphQL */`#graphql
8
+ query SupergraphSdl($apiKey: String!, $ref: String!, $ifAfterId: ID) {
9
+ routerConfig(ref: $ref, apiKey: $apiKey, ifAfterId: $ifAfterId) {
10
+ __typename
11
+ ... on RouterConfigResult {
12
+ id
13
+ supergraphSdl: supergraphSDL
14
+ }
15
+ ... on FetchError {
16
+ code
17
+ message
18
+ }
19
+ }
20
+ }
21
+ `;
22
+
23
+
24
+ type SupergraphSdlQueryResult =
25
+ | SupergraphSdlQuerySuccess
26
+ | SupergraphSdlQueryFailure;
27
+
28
+ interface SupergraphSdlQuerySuccess {
29
+ data: SupergraphSdlQuery;
30
+ }
31
+
32
+ interface SupergraphSdlQueryFailure {
33
+ data?: SupergraphSdlQuery;
34
+ errors: GraphQLError[];
35
+ }
36
+
37
+ const { name, version } = require('../package.json');
38
+
39
+ const fetchErrorMsg = "An error occurred while fetching your schema from Apollo: ";
40
+
41
+ export async function loadSupergraphSdlFromStorage({
42
+ graphRef,
43
+ apiKey,
44
+ endpoint,
45
+ fetcher,
46
+ compositionId,
47
+ }: {
48
+ graphRef: string;
49
+ apiKey: string;
50
+ endpoint: string;
51
+ fetcher: typeof fetch;
52
+ compositionId: string | null;
53
+ }) {
54
+ let result: Response;
55
+ const requestDetails = {
56
+ method: 'POST',
57
+ body: JSON.stringify({
58
+ query: SUPERGRAPH_SDL_QUERY,
59
+ variables: {
60
+ ref: graphRef,
61
+ apiKey,
62
+ ifAfterId: compositionId,
63
+ },
64
+ }),
65
+ headers: {
66
+ 'apollographql-client-name': name,
67
+ 'apollographql-client-version': version,
68
+ 'user-agent': `${name}/${version}`,
69
+ 'content-type': 'application/json',
70
+ },
71
+ };
72
+
73
+ const request: Request = new Request(endpoint, requestDetails);
74
+
75
+ const OOBReport = new OutOfBandReporter();
76
+ const startTime = new Date()
77
+ try {
78
+ result = await fetcher(endpoint, requestDetails);
79
+ } catch (e) {
80
+ const endTime = new Date();
81
+
82
+ await OOBReport.submitOutOfBandReportIfConfigured({
83
+ error: e,
84
+ request,
85
+ startedAt: startTime,
86
+ endedAt: endTime,
87
+ fetcher
88
+ });
89
+
90
+ throw new Error(fetchErrorMsg + (e.message ?? e));
91
+ }
92
+
93
+ const endTime = new Date();
94
+ let response: SupergraphSdlQueryResult;
95
+
96
+ if (result.ok || result.status === 400) {
97
+ try {
98
+ response = await result.json();
99
+ } catch (e) {
100
+ // Bad response
101
+ throw new Error(fetchErrorMsg + result.status + ' ' + e.message ?? e);
102
+ }
103
+
104
+ if ('errors' in response) {
105
+ throw new Error(
106
+ [fetchErrorMsg, ...response.errors.map((error) => error.message)].join(
107
+ '\n',
108
+ ),
109
+ );
110
+ }
111
+ } else {
112
+ await OOBReport.submitOutOfBandReportIfConfigured({
113
+ error: new Error(fetchErrorMsg + result.status + ' ' + result.statusText),
114
+ request,
115
+ response: result,
116
+ startedAt: startTime,
117
+ endedAt: endTime,
118
+ fetcher
119
+ });
120
+ throw new Error(fetchErrorMsg + result.status + ' ' + result.statusText);
121
+ }
122
+
123
+ const { routerConfig } = response.data;
124
+ if (routerConfig.__typename === 'RouterConfigResult') {
125
+ const {
126
+ id,
127
+ supergraphSdl,
128
+ // messages,
129
+ } = routerConfig;
130
+ return { id, supergraphSdl: supergraphSdl! };
131
+ } else if (routerConfig.__typename === 'FetchError') {
132
+ // FetchError case
133
+ const { code, message } = routerConfig;
134
+ throw new Error(`${code}: ${message}`);
135
+ } else if (routerConfig.__typename === 'Unchanged') {
136
+ return null;
137
+ } else {
138
+ throw new Error('Programming error: unhandled response failure');
139
+ }
140
+ }
@@ -36,7 +36,7 @@ declare module 'make-fetch-happen' {
36
36
  }
37
37
 
38
38
  export interface CacheManager {
39
- delete(req: Request): Promise<Boolean>;
39
+ delete(req: Request): Promise<boolean>;
40
40
  put(req: Request, res: Response): Promise<Response>;
41
41
  match(req: Request): Promise<Response | undefined>;
42
42
  }
@@ -52,7 +52,7 @@ declare module 'make-fetch-happen' {
52
52
  }
53
53
 
54
54
  let fetch: Fetcher & {
55
- defaults(opts?: RequestInit & FetcherOptions): Fetcher;
55
+ defaults(opts?: RequestInit & FetcherOptions): typeof fetch;
56
56
  };
57
57
 
58
58
  export default fetch;
@@ -0,0 +1,70 @@
1
+ import {
2
+ DocumentNode,
3
+ FragmentDefinitionNode,
4
+ GraphQLError,
5
+ GraphQLSchema,
6
+ Kind,
7
+ OperationDefinitionNode,
8
+ } from 'graphql';
9
+
10
+ type FragmentMap = { [fragmentName: string]: FragmentDefinitionNode };
11
+
12
+ export type OperationContext = {
13
+ schema: GraphQLSchema;
14
+ operation: OperationDefinitionNode;
15
+ fragments: FragmentMap;
16
+ };
17
+
18
+ // Adapted from buildExecutionContext in graphql-js
19
+ interface BuildOperationContextOptions {
20
+ schema: GraphQLSchema;
21
+ operationDocument: DocumentNode;
22
+ operationName?: string;
23
+ }
24
+
25
+ export function buildOperationContext({
26
+ schema,
27
+ operationDocument,
28
+ operationName,
29
+ }: BuildOperationContextOptions): OperationContext {
30
+ let operation: OperationDefinitionNode | undefined;
31
+ let operationCount = 0;
32
+ const fragments: {
33
+ [fragmentName: string]: FragmentDefinitionNode;
34
+ } = Object.create(null);
35
+ operationDocument.definitions.forEach(definition => {
36
+ switch (definition.kind) {
37
+ case Kind.OPERATION_DEFINITION:
38
+ operationCount++;
39
+ if (!operationName && operationCount > 1) {
40
+ throw new GraphQLError(
41
+ 'Must provide operation name if query contains ' +
42
+ 'multiple operations.',
43
+ );
44
+ }
45
+ if (
46
+ !operationName ||
47
+ (definition.name && definition.name.value === operationName)
48
+ ) {
49
+ operation = definition;
50
+ }
51
+ break;
52
+ case Kind.FRAGMENT_DEFINITION:
53
+ fragments[definition.name.value] = definition;
54
+ break;
55
+ }
56
+ });
57
+ if (!operation) {
58
+ if (operationName) {
59
+ throw new GraphQLError(`Unknown operation named "${operationName}".`);
60
+ } else {
61
+ throw new GraphQLError('Must provide an operation.');
62
+ }
63
+ }
64
+
65
+ return {
66
+ schema,
67
+ operation,
68
+ fragments,
69
+ };
70
+ }
@@ -0,0 +1,128 @@
1
+ import { fetch, Response, Request } from 'apollo-server-env';
2
+ import { GraphQLError } from 'graphql';
3
+ import {
4
+ ErrorCode,
5
+ OobReportMutation,
6
+ OobReportMutationVariables,
7
+ } from './__generated__/graphqlTypes';
8
+
9
+ // Magic /* GraphQL */ comment below is for codegen, do not remove
10
+ export const OUT_OF_BAND_REPORTER_QUERY = /* GraphQL */`#graphql
11
+ mutation OOBReport($input: APIMonitoringReport) {
12
+ reportError(report: $input)
13
+ }
14
+ `;
15
+
16
+ const { name, version } = require('../package.json');
17
+
18
+ type OobReportMutationResult =
19
+ | OobReportMutationSuccess
20
+ | OobReportMutationFailure;
21
+
22
+ interface OobReportMutationSuccess {
23
+ data: OobReportMutation;
24
+ }
25
+
26
+ interface OobReportMutationFailure {
27
+ data?: OobReportMutation;
28
+ errors: GraphQLError[];
29
+ }
30
+ export class OutOfBandReporter {
31
+ static endpoint: string | null = process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT || null;
32
+
33
+ async submitOutOfBandReportIfConfigured({
34
+ error,
35
+ request,
36
+ response,
37
+ startedAt,
38
+ endedAt,
39
+ tags,
40
+ fetcher,
41
+ }: {
42
+ error: Error;
43
+ request: Request;
44
+ response?: Response;
45
+ startedAt: Date;
46
+ endedAt: Date;
47
+ tags?: string[];
48
+ fetcher: typeof fetch;
49
+ }) {
50
+
51
+ // don't send report if the endpoint url is not configured
52
+ if (!OutOfBandReporter.endpoint) {
53
+ return;
54
+ }
55
+
56
+ let errorCode: ErrorCode;
57
+ if (!response) {
58
+ errorCode = ErrorCode.ConnectionFailed;
59
+ } else {
60
+ // possible error situations to check against
61
+ switch (response.status) {
62
+ case 400:
63
+ case 413:
64
+ case 422:
65
+ errorCode = ErrorCode.InvalidBody;
66
+ break;
67
+ case 408:
68
+ case 504:
69
+ errorCode = ErrorCode.Timeout;
70
+ break;
71
+ case 502:
72
+ case 503:
73
+ errorCode = ErrorCode.ConnectionFailed;
74
+ break;
75
+ default:
76
+ errorCode = ErrorCode.Other;
77
+ }
78
+ }
79
+
80
+ const responseBody: string | undefined = await response?.text();
81
+
82
+ const variables: OobReportMutationVariables = {
83
+ input: {
84
+ error: {
85
+ code: errorCode,
86
+ message: error.message,
87
+ },
88
+ request: {
89
+ url: request.url,
90
+ body: await request.text(),
91
+ },
92
+ response: response
93
+ ? {
94
+ httpStatusCode: response.status,
95
+ body: responseBody,
96
+ }
97
+ : null,
98
+ startedAt: startedAt.toISOString(),
99
+ endedAt: endedAt.toISOString(),
100
+ tags: tags,
101
+ },
102
+ };
103
+
104
+ try {
105
+ const oobResponse = await fetcher(OutOfBandReporter.endpoint, {
106
+ method: 'POST',
107
+ body: JSON.stringify({
108
+ query: OUT_OF_BAND_REPORTER_QUERY,
109
+ variables,
110
+ }),
111
+ headers: {
112
+ 'apollographql-client-name': name,
113
+ 'apollographql-client-version': version,
114
+ 'user-agent': `${name}/${version}`,
115
+ 'content-type': 'application/json',
116
+ },
117
+ });
118
+ const parsedResponse: OobReportMutationResult = await oobResponse.json();
119
+ if (!parsedResponse?.data?.reportError) {
120
+ throw new Error(
121
+ `Out-of-band error reporting failed: ${oobResponse.status} ${oobResponse.statusText}`,
122
+ );
123
+ }
124
+ } catch (e) {
125
+ throw new Error(`Out-of-band error reporting failed: ${e.message ?? e}`);
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,104 @@
1
+ import { execute, GraphQLError, parse } from "graphql";
2
+ import { cleanErrorOfInaccessibleNames } from "../cleanErrorOfInaccessibleNames";
3
+ import { buildSchema } from "@apollo/federation-internals";
4
+
5
+ describe('cleanErrorOfInaccessibleNames', () => {
6
+ const coreSchema = buildSchema(`
7
+ directive @core(
8
+ feature: String!,
9
+ as: String,
10
+ for: core__Purpose
11
+ ) repeatable on SCHEMA
12
+
13
+ directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION
14
+
15
+ schema
16
+ @core(feature: "https://specs.apollo.dev/core/v0.2")
17
+ @core(feature: "https://specs.apollo.dev/inaccessible/v0.1")
18
+ {
19
+ query: Query
20
+ }
21
+
22
+ enum core__Purpose {
23
+ EXECUTION
24
+ SECURITY
25
+ }
26
+
27
+ type Query {
28
+ fooField: Foo
29
+ bazField: Baz
30
+ }
31
+
32
+ interface Foo {
33
+ someField: String
34
+ }
35
+
36
+ type Bar implements Foo @inaccessible {
37
+ someField: String
38
+ }
39
+
40
+ type Bar2 @inaccessible {
41
+ anotherField: String
42
+ }
43
+
44
+ type Baz {
45
+ goodField: String
46
+ inaccessibleField: String @inaccessible
47
+ }
48
+ `);
49
+ const schema = coreSchema.toAPISchema().toGraphQLJSSchema();
50
+
51
+ it('removes inaccessible type names from error messages', async () => {
52
+ const result = await execute(schema, parse('{fooField{someField}}'), {
53
+ fooField: {
54
+ __typename: 'Bar',
55
+ someField: 'test',
56
+ },
57
+ });
58
+
59
+ const cleaned = cleanErrorOfInaccessibleNames(schema, result.errors![0]!);
60
+ expect(cleaned.message).toMatchInlineSnapshot(
61
+ `"Abstract type \\"Foo\\" was resolve to a type [inaccessible type] that does not exist inside schema."`,
62
+ );
63
+ });
64
+
65
+ it('removes multiple/repeated inaccessible type names from error messages', async () => {
66
+ const contrivedError = new GraphQLError(
67
+ `Something something "Bar" and "Bar" again, as well as "Bar2".`,
68
+ );
69
+ const cleaned = cleanErrorOfInaccessibleNames(schema, contrivedError);
70
+ expect(cleaned.message).toMatchInlineSnapshot(
71
+ `"Something something [inaccessible type] and [inaccessible type] again, as well as [inaccessible type]."`,
72
+ );
73
+ });
74
+
75
+ it('removes inaccessible field names from error messages', async () => {
76
+ const contrivedError = new GraphQLError(
77
+ `Can't query inaccessible field "Baz.inaccessibleField".`,
78
+ );
79
+ const cleaned = cleanErrorOfInaccessibleNames(schema, contrivedError);
80
+ expect(cleaned.message).toMatchInlineSnapshot(
81
+ `"Can't query inaccessible field [inaccessible field]."`,
82
+ );
83
+ });
84
+
85
+ it('removes multiple/repeated inaccessible field names from error messages', async () => {
86
+ const contrivedError = new GraphQLError(
87
+ `Can't query inaccessible field "Baz.inaccessibleField" and "Baz.inaccessibleField", as well as "Bar2.anotherField".`,
88
+ );
89
+ const cleaned = cleanErrorOfInaccessibleNames(schema, contrivedError);
90
+ expect(cleaned.message).toMatchInlineSnapshot(
91
+ `"Can't query inaccessible field [inaccessible field] and [inaccessible field], as well as [inaccessible field]."`,
92
+ );
93
+ });
94
+
95
+ it("doesn't remove special-case double-quoted words from graphql error messages", () => {
96
+ const graphqlError = new GraphQLError(
97
+ `Something something "resolveType" something something "isTypeOf".`,
98
+ );
99
+ const cleaned = cleanErrorOfInaccessibleNames(schema, graphqlError);
100
+ expect(cleaned.message).toMatchInlineSnapshot(
101
+ `"Something something \\"resolveType\\" something something \\"isTypeOf\\"."`,
102
+ );
103
+ });
104
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../tsconfig.test",
3
+ "include": ["**/*"],
4
+ "references": [
5
+ { "path": "../../.." },
6
+ { "path": "../../../../federation-integration-testsuite-js" },
7
+ ]
8
+ }
@@ -1,4 +1,8 @@
1
- import { isNotNullOrUndefined } from 'apollo-env';
1
+ export function isNotNullOrUndefined<T>(
2
+ value: T | null | undefined,
3
+ ): value is T {
4
+ return value !== null && typeof value !== 'undefined';
5
+ }
2
6
 
3
7
  export function compactMap<T, U>(
4
8
  array: T[],
@@ -16,32 +20,6 @@ export function compactMap<T, U>(
16
20
  );
17
21
  }
18
22
 
19
- export function partition<T, U extends T>(
20
- array: T[],
21
- predicate: (element: T, index: number, array: T[]) => element is U,
22
- ): [U[], T[]];
23
- export function partition<T>(
24
- array: T[],
25
- predicate: (element: T, index: number, array: T[]) => boolean,
26
- ): [T[], T[]];
27
- export function partition<T>(
28
- array: T[],
29
- predicate: (element: T, index: number, array: T[]) => boolean,
30
- ): [T[], T[]] {
31
- array.map;
32
- return array.reduce(
33
- (accumulator, element, index) => {
34
- return (
35
- predicate(element, index, array)
36
- ? accumulator[0].push(element)
37
- : accumulator[1].push(element),
38
- accumulator
39
- );
40
- },
41
- [[], []] as [T[], T[]],
42
- );
43
- }
44
-
45
23
  export function findAndExtract<T>(
46
24
  array: T[],
47
25
  predicate: (element: T, index: number, array: T[]) => boolean,
@@ -49,7 +27,7 @@ export function findAndExtract<T>(
49
27
  const index = array.findIndex(predicate);
50
28
  if (index === -1) return [undefined, array];
51
29
 
52
- let remaining = array.slice(0, index);
30
+ const remaining = array.slice(0, index);
53
31
  if (index < array.length - 1) {
54
32
  remaining.push(...array.slice(index + 1));
55
33
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * For lack of a "home of federation utilities", this function is copy/pasted
3
+ * verbatim across the federation, gateway, and query-planner packages. Any changes
4
+ * made here should be reflected in the other two locations as well.
5
+ *
6
+ * @param condition
7
+ * @param message
8
+ * @throws {@link Error}
9
+ */
10
+ export function assert(condition: any, message: string): asserts condition {
11
+ if (!condition) {
12
+ throw new Error(message);
13
+ }
14
+ }
@@ -0,0 +1,29 @@
1
+ import { GraphQLError, GraphQLSchema } from 'graphql';
2
+
3
+ export function cleanErrorOfInaccessibleNames(
4
+ schema: GraphQLSchema,
5
+ error: GraphQLError,
6
+ ): GraphQLError {
7
+
8
+ const typeDotFieldRegex = /"([_A-Za-z][_0-9A-Za-z]*)\.([_A-Za-z][_0-9A-Za-z]*)"/g;
9
+ error.message = error.message.replace(typeDotFieldRegex, (match: string) => {
10
+ const [typeName, fieldName] = match.replace(/"/g, '',).split('.');
11
+ const type = schema.getType(typeName);
12
+ if (!type) {
13
+ return '[inaccessible field]';
14
+ } else {
15
+ const field = 'getFields' in type ? type.getFields()[fieldName] : null;
16
+ return field ? match : '[inaccessible field]';
17
+ }
18
+ });
19
+
20
+ const typeRegex = /"([_A-Za-z][_0-9A-Za-z]*)"/g;
21
+ error.message = error.message.replace(typeRegex, (match: string) => {
22
+ // Special cases in graphql-js that happen to match our regex.
23
+ if (match === '"isTypeOf"' || match === '"resolveType"') return match;
24
+ const typeName = match.replace(/"/g, '',);
25
+ return schema.getType(typeName) ? match : '[inaccessible type]';
26
+ })
27
+
28
+ return error;
29
+ }