@apollo/gateway 0.45.0 → 0.47.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.
Files changed (124) hide show
  1. package/dist/config.d.ts +42 -16
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +28 -18
  4. package/dist/config.js.map +1 -1
  5. package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
  6. package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
  7. package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
  8. package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
  9. package/dist/executeQueryPlan.d.ts.map +1 -1
  10. package/dist/executeQueryPlan.js +2 -2
  11. package/dist/executeQueryPlan.js.map +1 -1
  12. package/dist/index.d.ts +35 -23
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +205 -308
  15. package/dist/index.js.map +1 -1
  16. package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
  17. package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
  18. package/dist/schema-helper/addResolversToSchema.js +62 -0
  19. package/dist/schema-helper/addResolversToSchema.js.map +1 -0
  20. package/dist/schema-helper/error.d.ts +6 -0
  21. package/dist/schema-helper/error.d.ts.map +1 -0
  22. package/dist/schema-helper/error.js +14 -0
  23. package/dist/schema-helper/error.js.map +1 -0
  24. package/dist/schema-helper/index.d.ts +4 -0
  25. package/dist/schema-helper/index.d.ts.map +1 -0
  26. package/dist/schema-helper/index.js +16 -0
  27. package/dist/schema-helper/index.js.map +1 -0
  28. package/dist/schema-helper/resolverMap.d.ts +16 -0
  29. package/dist/schema-helper/resolverMap.d.ts.map +1 -0
  30. package/dist/schema-helper/resolverMap.js +3 -0
  31. package/dist/schema-helper/resolverMap.js.map +1 -0
  32. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
  33. package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
  34. package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
  35. package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
  36. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
  37. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
  38. package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
  39. package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
  40. package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
  41. package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
  42. package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
  43. package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
  44. package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
  45. package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
  46. package/dist/supergraphManagers/LocalCompose/index.js +55 -0
  47. package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
  48. package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
  49. package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
  50. package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
  51. package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
  52. package/dist/{loadSupergraphSdlFromStorage.d.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts} +1 -1
  53. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
  54. package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +1 -1
  55. package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
  56. package/dist/{outOfBandReporter.d.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts} +0 -0
  57. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
  58. package/dist/{outOfBandReporter.js → supergraphManagers/UplinkFetcher/outOfBandReporter.js} +2 -2
  59. package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
  60. package/dist/supergraphManagers/index.d.ts +5 -0
  61. package/dist/supergraphManagers/index.d.ts.map +1 -0
  62. package/dist/supergraphManagers/index.js +12 -0
  63. package/dist/supergraphManagers/index.js.map +1 -0
  64. package/dist/utilities/createHash.d.ts +2 -0
  65. package/dist/utilities/createHash.d.ts.map +1 -0
  66. package/dist/utilities/createHash.js +15 -0
  67. package/dist/utilities/createHash.js.map +1 -0
  68. package/dist/utilities/isNodeLike.d.ts +3 -0
  69. package/dist/utilities/isNodeLike.d.ts.map +1 -0
  70. package/dist/utilities/isNodeLike.js +8 -0
  71. package/dist/utilities/isNodeLike.js.map +1 -0
  72. package/package.json +12 -10
  73. package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
  74. package/src/__tests__/executeQueryPlan.test.ts +637 -1
  75. package/src/__tests__/execution-utils.ts +3 -3
  76. package/src/__tests__/gateway/buildService.test.ts +3 -3
  77. package/src/__tests__/gateway/endToEnd.test.ts +1 -1
  78. package/src/__tests__/gateway/executor.test.ts +1 -1
  79. package/src/__tests__/gateway/lifecycle-hooks.test.ts +58 -99
  80. package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
  81. package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
  82. package/src/__tests__/gateway/reporting.test.ts +44 -19
  83. package/src/__tests__/gateway/supergraphSdl.test.ts +394 -0
  84. package/src/__tests__/integration/aliases.test.ts +9 -3
  85. package/src/__tests__/integration/configuration.test.ts +109 -12
  86. package/src/__tests__/integration/logger.test.ts +1 -1
  87. package/src/__tests__/integration/networkRequests.test.ts +81 -118
  88. package/src/__tests__/integration/nockMocks.ts +15 -8
  89. package/src/config.ts +149 -40
  90. package/src/datasources/LocalGraphQLDataSource.ts +1 -1
  91. package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
  92. package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
  93. package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
  94. package/src/executeQueryPlan.ts +14 -2
  95. package/src/index.ts +314 -485
  96. package/src/schema-helper/addResolversToSchema.ts +83 -0
  97. package/src/schema-helper/error.ts +11 -0
  98. package/src/schema-helper/index.ts +3 -0
  99. package/src/schema-helper/resolverMap.ts +23 -0
  100. package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
  101. package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +5 -5
  102. package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
  103. package/src/supergraphManagers/IntrospectAndCompose/index.ts +163 -0
  104. package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +6 -6
  105. package/src/supergraphManagers/LegacyFetcher/index.ts +229 -0
  106. package/src/supergraphManagers/LocalCompose/index.ts +83 -0
  107. package/src/{__tests__ → supergraphManagers/UplinkFetcher/__tests__}/loadSupergraphSdlFromStorage.test.ts +4 -4
  108. package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
  109. package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
  110. package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +3 -3
  111. package/src/{outOfBandReporter.ts → supergraphManagers/UplinkFetcher/outOfBandReporter.ts} +2 -2
  112. package/src/supergraphManagers/index.ts +4 -0
  113. package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +3 -3
  114. package/src/utilities/createHash.ts +10 -0
  115. package/src/utilities/isNodeLike.ts +11 -0
  116. package/CHANGELOG.md +0 -470
  117. package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
  118. package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
  119. package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
  120. package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
  121. package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
  122. package/dist/outOfBandReporter.d.ts.map +0 -1
  123. package/dist/outOfBandReporter.js.map +0 -1
  124. package/src/__tests__/gateway/composedSdl.test.ts +0 -44
@@ -1,16 +1,16 @@
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, GraphQLDataSourceRequestKind } from './datasources/types';
5
- import { SERVICE_DEFINITION_QUERY } from './';
6
- import { CompositionUpdate, ServiceEndpointDefinition } from './config';
4
+ import { GraphQLDataSource, GraphQLDataSourceRequestKind } from '../../datasources/types';
5
+ import { SERVICE_DEFINITION_QUERY } from '../..';
6
+ import { ServiceDefinitionUpdate, ServiceEndpointDefinition } from '../../config';
7
7
  import { ServiceDefinition } from '@apollo/federation';
8
8
 
9
- type Service = ServiceEndpointDefinition & {
9
+ export type Service = ServiceEndpointDefinition & {
10
10
  dataSource: GraphQLDataSource;
11
11
  };
12
12
 
13
- export async function getServiceDefinitionsFromRemoteEndpoint({
13
+ export async function loadServicesFromRemoteEndpoint({
14
14
  serviceList,
15
15
  getServiceIntrospectionHeaders,
16
16
  serviceSdlCache,
@@ -20,7 +20,7 @@ export async function getServiceDefinitionsFromRemoteEndpoint({
20
20
  service: ServiceEndpointDefinition,
21
21
  ) => Promise<HeadersInit | undefined>;
22
22
  serviceSdlCache: Map<string, string>;
23
- }): Promise<CompositionUpdate> {
23
+ }): Promise<ServiceDefinitionUpdate> {
24
24
  if (!serviceList || !serviceList.length) {
25
25
  throw new Error(
26
26
  'Tried to load services from remote endpoints but none provided',
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Similar in concept to `IntrospectAndCompose`, but this handles
3
+ * the `experimental_updateComposition` and `experimental_updateSupergraphSdl`
4
+ * configuration options of the gateway and will be removed in a future release
5
+ * along with those options.
6
+ */
7
+ import { Logger } from 'apollo-server-types';
8
+ import resolvable from '@josephg/resolvable';
9
+ import {
10
+ SupergraphManager,
11
+ SupergraphSdlHookOptions,
12
+ DynamicGatewayConfig,
13
+ isSupergraphSdlUpdate,
14
+ isServiceDefinitionUpdate,
15
+ ServiceDefinitionUpdate,
16
+ GetDataSourceFunction,
17
+ } from '../../config';
18
+ import {
19
+ Experimental_UpdateComposition,
20
+ SubgraphHealthCheckFunction,
21
+ SupergraphSdlUpdateFunction,
22
+ } from '../..';
23
+ import {
24
+ composeAndValidate,
25
+ compositionHasErrors,
26
+ ServiceDefinition,
27
+ } from '@apollo/federation';
28
+
29
+ export interface LegacyFetcherOptions {
30
+ pollIntervalInMs?: number;
31
+ logger?: Logger;
32
+ subgraphHealthCheck?: boolean;
33
+ updateServiceDefinitions: Experimental_UpdateComposition;
34
+ gatewayConfig: DynamicGatewayConfig;
35
+ }
36
+
37
+ type State =
38
+ | { phase: 'initialized' }
39
+ | { phase: 'polling'; pollingPromise?: Promise<void> }
40
+ | { phase: 'stopped' };
41
+
42
+ export class LegacyFetcher implements SupergraphManager {
43
+ private config: LegacyFetcherOptions;
44
+ private update?: SupergraphSdlUpdateFunction;
45
+ private healthCheck?: SubgraphHealthCheckFunction;
46
+ private getDataSource?: GetDataSourceFunction;
47
+ private timerRef: NodeJS.Timeout | null = null;
48
+ private state: State;
49
+ private compositionId?: string;
50
+ private serviceDefinitions?: ServiceDefinition[];
51
+
52
+ constructor(options: LegacyFetcherOptions) {
53
+ this.config = options;
54
+ this.state = { phase: 'initialized' };
55
+ this.issueDeprecationWarnings();
56
+ }
57
+
58
+ private issueDeprecationWarnings() {
59
+ if ('experimental_updateSupergraphSdl' in this.config.gatewayConfig) {
60
+ this.config.logger?.warn(
61
+ 'The `experimental_updateSupergraphSdl` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the function form of the `supergraphSdl` configuration option.',
62
+ );
63
+ }
64
+
65
+ if ('experimental_updateServiceDefinitions' in this.config.gatewayConfig) {
66
+ this.config.logger?.warn(
67
+ 'The `experimental_updateServiceDefinitions` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the function form of the `supergraphSdl` configuration option.',
68
+ );
69
+ }
70
+ }
71
+
72
+ public async initialize({
73
+ update,
74
+ healthCheck,
75
+ getDataSource,
76
+ }: SupergraphSdlHookOptions) {
77
+ this.update = update;
78
+ this.getDataSource = getDataSource;
79
+
80
+ if (this.config.subgraphHealthCheck) {
81
+ this.healthCheck = healthCheck;
82
+ }
83
+
84
+ let initialSupergraphSdl: string | null = null;
85
+ try {
86
+ initialSupergraphSdl = await this.updateSupergraphSdl();
87
+ } catch (e) {
88
+ this.logUpdateFailure(e);
89
+ throw e;
90
+ }
91
+
92
+ // Start polling after we resolve the first supergraph
93
+ if (this.config.pollIntervalInMs) {
94
+ this.beginPolling();
95
+ }
96
+
97
+ return {
98
+ // on init, this supergraphSdl should never actually be `null`.
99
+ // `this.updateSupergraphSdl()` will only return null if the schema hasn't
100
+ // changed over the course of an _update_.
101
+ supergraphSdl: initialSupergraphSdl!,
102
+ cleanup: async () => {
103
+ if (this.state.phase === 'polling') {
104
+ await this.state.pollingPromise;
105
+ }
106
+ this.state = { phase: 'stopped' };
107
+ if (this.timerRef) {
108
+ clearTimeout(this.timerRef);
109
+ this.timerRef = null;
110
+ }
111
+ },
112
+ };
113
+ }
114
+
115
+ private async updateSupergraphSdl() {
116
+ const result = await this.config.updateServiceDefinitions(
117
+ this.config.gatewayConfig,
118
+ );
119
+
120
+ if (isSupergraphSdlUpdate(result)) {
121
+ // no change
122
+ if (this.compositionId === result.id) return null;
123
+
124
+ await this.healthCheck?.(result.supergraphSdl);
125
+ this.compositionId = result.id;
126
+ return result.supergraphSdl;
127
+ } else if (isServiceDefinitionUpdate(result)) {
128
+ const supergraphSdl = this.updateByComposition(result);
129
+ if (!supergraphSdl) return null;
130
+ await this.healthCheck?.(supergraphSdl);
131
+ return supergraphSdl;
132
+ } else {
133
+ throw new Error(
134
+ 'Programming error: unexpected result type from `updateServiceDefinitions`',
135
+ );
136
+ }
137
+ }
138
+
139
+ private updateByComposition(result: ServiceDefinitionUpdate) {
140
+ if (
141
+ !result.serviceDefinitions ||
142
+ JSON.stringify(this.serviceDefinitions) ===
143
+ JSON.stringify(result.serviceDefinitions)
144
+ ) {
145
+ this.config.logger?.debug(
146
+ 'No change in service definitions since last check.',
147
+ );
148
+ return null;
149
+ }
150
+
151
+ if (this.serviceDefinitions) {
152
+ this.config.logger?.info('New service definitions were found.');
153
+ }
154
+
155
+ this.serviceDefinitions = result.serviceDefinitions;
156
+
157
+ const supergraphSdl = this.createSupergraphFromServiceList(
158
+ result.serviceDefinitions,
159
+ );
160
+
161
+ if (!supergraphSdl) {
162
+ throw new Error(
163
+ "A valid schema couldn't be composed. Falling back to previous schema.",
164
+ );
165
+ } else {
166
+ return supergraphSdl;
167
+ }
168
+ }
169
+
170
+ private createSupergraphFromServiceList(serviceList: ServiceDefinition[]) {
171
+ this.config.logger?.debug(
172
+ `Composing schema from service list: \n${serviceList
173
+ .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
174
+ .join('\n')}`,
175
+ );
176
+
177
+ const compositionResult = composeAndValidate(serviceList);
178
+
179
+ if (compositionHasErrors(compositionResult)) {
180
+ const { errors } = compositionResult;
181
+ throw Error(
182
+ "A valid schema couldn't be composed. The following composition errors were found:\n" +
183
+ errors.map((e) => '\t' + e.message).join('\n'),
184
+ );
185
+ } else {
186
+ const { supergraphSdl } = compositionResult;
187
+ for (const service of serviceList) {
188
+ this.getDataSource?.(service);
189
+ }
190
+
191
+ this.config.logger?.debug('Schema loaded and ready for execution');
192
+
193
+ return supergraphSdl;
194
+ }
195
+ }
196
+
197
+ private beginPolling() {
198
+ this.state = { phase: 'polling' };
199
+ this.poll();
200
+ }
201
+
202
+ private poll() {
203
+ this.timerRef = setTimeout(async () => {
204
+ if (this.state.phase === 'polling') {
205
+ const pollingPromise = resolvable();
206
+
207
+ this.state.pollingPromise = pollingPromise;
208
+ try {
209
+ const maybeNewSupergraphSdl = await this.updateSupergraphSdl();
210
+ if (maybeNewSupergraphSdl) {
211
+ this.update?.(maybeNewSupergraphSdl);
212
+ }
213
+ } catch (e) {
214
+ this.logUpdateFailure(e);
215
+ }
216
+ pollingPromise.resolve();
217
+ }
218
+
219
+ this.poll();
220
+ }, this.config.pollIntervalInMs!);
221
+ }
222
+
223
+ private logUpdateFailure(e: any) {
224
+ this.config.logger?.error(
225
+ 'UplinkFetcher failed to update supergraph with the following error: ' +
226
+ (e.message ?? e),
227
+ );
228
+ }
229
+ }
@@ -0,0 +1,83 @@
1
+ // TODO(trevor:removeServiceList) the whole file goes away
2
+ import { Logger } from 'apollo-server-types';
3
+ import {
4
+ composeAndValidate,
5
+ compositionHasErrors,
6
+ ServiceDefinition,
7
+ } from '@apollo/federation';
8
+ import {
9
+ GetDataSourceFunction,
10
+ SupergraphSdlHookOptions,
11
+ SupergraphManager,
12
+ } from '../../config';
13
+
14
+ export interface LocalComposeOptions {
15
+ logger?: Logger;
16
+ localServiceList: ServiceDefinition[];
17
+ }
18
+
19
+ export class LocalCompose implements SupergraphManager {
20
+ private config: LocalComposeOptions;
21
+ private getDataSource?: GetDataSourceFunction;
22
+
23
+ constructor(options: LocalComposeOptions) {
24
+ this.config = options;
25
+ this.issueDeprecationWarnings();
26
+ }
27
+
28
+ private issueDeprecationWarnings() {
29
+ this.config.logger?.warn(
30
+ 'The `localServiceList` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the `LocalCompose` supergraph manager exported by `@apollo/gateway`.',
31
+ );
32
+ }
33
+
34
+ public async initialize({ getDataSource }: SupergraphSdlHookOptions) {
35
+ this.getDataSource = getDataSource;
36
+ let supergraphSdl: string | null = null;
37
+ try {
38
+ supergraphSdl = this.createSupergraphFromServiceList(
39
+ this.config.localServiceList,
40
+ );
41
+ } catch (e) {
42
+ this.logUpdateFailure(e);
43
+ throw e;
44
+ }
45
+ return {
46
+ supergraphSdl,
47
+ };
48
+ }
49
+
50
+ private createSupergraphFromServiceList(serviceList: ServiceDefinition[]) {
51
+ this.config.logger?.debug(
52
+ `Composing schema from service list: \n${serviceList
53
+ .map(({ name, url }) => ` ${url || 'local'}: ${name}`)
54
+ .join('\n')}`,
55
+ );
56
+
57
+ const compositionResult = composeAndValidate(serviceList);
58
+
59
+ if (compositionHasErrors(compositionResult)) {
60
+ const { errors } = compositionResult;
61
+ throw Error(
62
+ "A valid schema couldn't be composed. The following composition errors were found:\n" +
63
+ errors.map((e) => '\t' + e.message).join('\n'),
64
+ );
65
+ } else {
66
+ const { supergraphSdl } = compositionResult;
67
+ for (const service of serviceList) {
68
+ this.getDataSource?.(service);
69
+ }
70
+
71
+ this.config.logger?.debug('Schema loaded and ready for execution');
72
+
73
+ return supergraphSdl;
74
+ }
75
+ }
76
+
77
+ private logUpdateFailure(e: any) {
78
+ this.config.logger?.error(
79
+ 'UplinkFetcher failed to update supergraph with the following error: ' +
80
+ (e.message ?? e),
81
+ );
82
+ }
83
+ }
@@ -2,7 +2,7 @@ import {
2
2
  loadSupergraphSdlFromStorage,
3
3
  loadSupergraphSdlFromUplinks
4
4
  } from '../loadSupergraphSdlFromStorage';
5
- import { getDefaultFetcher } from '../..';
5
+ import { getDefaultFetcher } from '../../..';
6
6
  import {
7
7
  graphRef,
8
8
  apiKey,
@@ -14,9 +14,9 @@ import {
14
14
  mockSupergraphSdlRequestSuccess,
15
15
  mockSupergraphSdlRequestIfAfterUnchanged,
16
16
  mockSupergraphSdlRequestIfAfter
17
- } from './integration/nockMocks';
18
- import { getTestingSupergraphSdl } from "./execution-utils";
19
- import { nockAfterEach, nockBeforeEach } from './nockAssertions';
17
+ } from '../../../__tests__/integration/nockMocks';
18
+ import { getTestingSupergraphSdl } from "../../../__tests__/execution-utils";
19
+ import { nockAfterEach, nockBeforeEach } from '../../../__tests__/nockAssertions';
20
20
 
21
21
  describe('loadSupergraphSdlFromStorage', () => {
22
22
  beforeEach(nockBeforeEach);
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../../../tsconfig.test",
3
+ "include": ["**/*"],
4
+ "references": [
5
+ { "path": "../../../../" },
6
+ { "path": "../../../../../federation-integration-testsuite-js" },
7
+ ]
8
+ }
@@ -0,0 +1,128 @@
1
+ import { fetch } from 'apollo-server-env';
2
+ import { Logger } from 'apollo-server-types';
3
+ import resolvable from '@josephg/resolvable';
4
+ import { SupergraphManager, SupergraphSdlHookOptions } from '../../config';
5
+ import { SubgraphHealthCheckFunction, SupergraphSdlUpdateFunction } from '../..';
6
+ import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
7
+
8
+ export interface UplinkFetcherOptions {
9
+ pollIntervalInMs: number;
10
+ subgraphHealthCheck?: boolean;
11
+ graphRef: string;
12
+ apiKey: string;
13
+ fetcher: typeof fetch;
14
+ maxRetries: number;
15
+ uplinkEndpoints: string[];
16
+ logger?: Logger;
17
+ }
18
+
19
+ type State =
20
+ | { phase: 'initialized' }
21
+ | { phase: 'polling'; pollingPromise?: Promise<void> }
22
+ | { phase: 'stopped' };
23
+
24
+ export class UplinkFetcher implements SupergraphManager {
25
+ private config: UplinkFetcherOptions;
26
+ private update?: SupergraphSdlUpdateFunction;
27
+ private healthCheck?: SubgraphHealthCheckFunction;
28
+ private timerRef: NodeJS.Timeout | null = null;
29
+ private state: State;
30
+ private errorReportingEndpoint: string | undefined =
31
+ process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
32
+ private compositionId?: string;
33
+
34
+ constructor(options: UplinkFetcherOptions) {
35
+ this.config = options;
36
+ this.state = { phase: 'initialized' };
37
+ }
38
+
39
+ public async initialize({ update, healthCheck }: SupergraphSdlHookOptions) {
40
+ this.update = update;
41
+
42
+ if (this.config.subgraphHealthCheck) {
43
+ this.healthCheck = healthCheck;
44
+ }
45
+
46
+ let initialSupergraphSdl: string | null = null;
47
+ try {
48
+ initialSupergraphSdl = await this.updateSupergraphSdl();
49
+ } catch (e) {
50
+ this.logUpdateFailure(e);
51
+ throw e;
52
+ }
53
+
54
+ // Start polling after we resolve the first supergraph
55
+ this.beginPolling();
56
+
57
+ return {
58
+ // on init, this supergraphSdl should never actually be `null`.
59
+ // `this.updateSupergraphSdl()` will only return null if the schema hasn't
60
+ // changed over the course of an _update_.
61
+ supergraphSdl: initialSupergraphSdl!,
62
+ cleanup: async () => {
63
+ if (this.state.phase === 'polling') {
64
+ await this.state.pollingPromise;
65
+ }
66
+ this.state = { phase: 'stopped' };
67
+ if (this.timerRef) {
68
+ clearTimeout(this.timerRef);
69
+ this.timerRef = null;
70
+ }
71
+ },
72
+ };
73
+ }
74
+
75
+ private async updateSupergraphSdl() {
76
+ const result = await loadSupergraphSdlFromUplinks({
77
+ graphRef: this.config.graphRef,
78
+ apiKey: this.config.apiKey,
79
+ endpoints: this.config.uplinkEndpoints,
80
+ errorReportingEndpoint: this.errorReportingEndpoint,
81
+ fetcher: this.config.fetcher,
82
+ compositionId: this.compositionId ?? null,
83
+ maxRetries: this.config.maxRetries,
84
+ });
85
+
86
+ if (!result) {
87
+ return null;
88
+ } else {
89
+ this.compositionId = result.id;
90
+ // the healthCheck fn is only assigned if it's enabled in the config
91
+ await this.healthCheck?.(result.supergraphSdl);
92
+ return result.supergraphSdl;
93
+ }
94
+ }
95
+
96
+ private beginPolling() {
97
+ this.state = { phase: 'polling' };
98
+ this.poll();
99
+ }
100
+
101
+ private poll() {
102
+ this.timerRef = setTimeout(async () => {
103
+ if (this.state.phase === 'polling') {
104
+ const pollingPromise = resolvable();
105
+
106
+ this.state.pollingPromise = pollingPromise;
107
+ try {
108
+ const maybeNewSupergraphSdl = await this.updateSupergraphSdl();
109
+ if (maybeNewSupergraphSdl) {
110
+ this.update?.(maybeNewSupergraphSdl);
111
+ }
112
+ } catch (e) {
113
+ this.logUpdateFailure(e);
114
+ }
115
+ pollingPromise.resolve();
116
+ }
117
+
118
+ this.poll();
119
+ }, this.config.pollIntervalInMs);
120
+ }
121
+
122
+ private logUpdateFailure(e: any) {
123
+ this.config.logger?.error(
124
+ 'UplinkFetcher failed to update supergraph with the following error: ' +
125
+ (e.message ?? e),
126
+ );
127
+ }
128
+ }
@@ -1,8 +1,8 @@
1
1
  import { fetch, Response, Request } from 'apollo-server-env';
2
2
  import { GraphQLError } from 'graphql';
3
- import { SupergraphSdlUpdate } from './config';
3
+ import { SupergraphSdlUpdate } from '../../config';
4
4
  import { submitOutOfBandReportIfConfigured } from './outOfBandReporter';
5
- import { SupergraphSdlQuery } from './__generated__/graphqlTypes';
5
+ import { SupergraphSdlQuery } from '../../__generated__/graphqlTypes';
6
6
 
7
7
  // Magic /* GraphQL */ comment below is for codegen, do not remove
8
8
  export const SUPERGRAPH_SDL_QUERY = /* GraphQL */`#graphql
@@ -35,7 +35,7 @@ interface SupergraphSdlQueryFailure {
35
35
  errors: GraphQLError[];
36
36
  }
37
37
 
38
- const { name, version } = require('../package.json');
38
+ const { name, version } = require('../../../package.json');
39
39
 
40
40
  const fetchErrorMsg = "An error occurred while fetching your schema from Apollo: ";
41
41
 
@@ -4,7 +4,7 @@ import {
4
4
  ErrorCode,
5
5
  OobReportMutation,
6
6
  OobReportMutationVariables,
7
- } from './__generated__/graphqlTypes';
7
+ } from '../../__generated__/graphqlTypes';
8
8
 
9
9
  // Magic /* GraphQL */ comment below is for codegen, do not remove
10
10
  export const OUT_OF_BAND_REPORTER_QUERY = /* GraphQL */`#graphql
@@ -13,7 +13,7 @@ export const OUT_OF_BAND_REPORTER_QUERY = /* GraphQL */`#graphql
13
13
  }
14
14
  `;
15
15
 
16
- const { name, version } = require('../package.json');
16
+ const { name, version } = require('../../../package.json');
17
17
 
18
18
  type OobReportMutationResult =
19
19
  | OobReportMutationSuccess
@@ -0,0 +1,4 @@
1
+ export { LocalCompose } from './LocalCompose';
2
+ export { LegacyFetcher } from './LegacyFetcher';
3
+ export { IntrospectAndCompose } from './IntrospectAndCompose';
4
+ export { UplinkFetcher } from './UplinkFetcher';
@@ -49,16 +49,16 @@ describe('cleanErrorOfInaccessibleNames', () => {
49
49
  schema = toAPISchema(schema);
50
50
 
51
51
  it('removes inaccessible type names from error messages', async () => {
52
- const result = await execute(schema, parse('{fooField{someField}}'), {
52
+ const result = await execute({ schema, document: parse('{fooField{someField}}'), rootValue: {
53
53
  fooField: {
54
54
  __typename: 'Bar',
55
55
  someField: 'test',
56
56
  },
57
- });
57
+ }});
58
58
 
59
59
  const cleaned = cleanErrorOfInaccessibleNames(schema, result.errors?.[0]!);
60
60
  expect(cleaned.message).toMatchInlineSnapshot(
61
- `"Abstract type \\"Foo\\" was resolve to a type [inaccessible type] that does not exist inside schema."`,
61
+ `"Abstract type \\"Foo\\" was resolved to a type [inaccessible type] that does not exist inside the schema."`,
62
62
  );
63
63
  });
64
64
 
@@ -0,0 +1,10 @@
1
+ import isNodeLike from './isNodeLike';
2
+
3
+ export function createHash (kind: string): import('crypto').Hash {
4
+ if (isNodeLike) {
5
+ // Use module.require instead of just require to avoid bundling whatever
6
+ // crypto polyfills a non-Node bundler might fall back to.
7
+ return module.require('crypto').createHash(kind);
8
+ }
9
+ return require('sha.js')(kind);
10
+ }
@@ -0,0 +1,11 @@
1
+ export default typeof process === 'object' &&
2
+ process &&
3
+ // We used to check `process.release.name === "node"`, however that doesn't
4
+ // account for certain forks of Node.js which are otherwise identical to
5
+ // Node.js. For example, NodeSource's N|Solid reports itself as "nsolid",
6
+ // though it's mostly the same build of Node.js with an extra addon.
7
+ process.release &&
8
+ process.versions &&
9
+ // The one thing which is present on both Node.js and N|Solid (a fork of
10
+ // Node.js), is `process.versions.node` being defined.
11
+ typeof process.versions.node === 'string';