@apollo/gateway 2.0.0-alpha.0 → 2.0.0-alpha.4
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/README.md +1 -1
- package/dist/__generated__/graphqlTypes.d.ts +13 -11
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/config.d.ts +45 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -31
- package/dist/config.js.map +1 -1
- package/dist/datasources/LocalGraphQLDataSource.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +4 -1
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/types.d.ts +1 -1
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +6 -6
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +36 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -297
- package/dist/index.js.map +1 -1
- package/dist/operationContext.js +0 -1
- package/dist/operationContext.js.map +1 -1
- package/dist/schema-helper/addResolversToSchema.d.ts +4 -0
- package/dist/schema-helper/addResolversToSchema.d.ts.map +1 -0
- package/dist/schema-helper/addResolversToSchema.js +62 -0
- package/dist/schema-helper/addResolversToSchema.js.map +1 -0
- package/dist/schema-helper/error.d.ts +6 -0
- package/dist/schema-helper/error.d.ts.map +1 -0
- package/dist/schema-helper/error.js +14 -0
- package/dist/schema-helper/error.js.map +1 -0
- package/dist/schema-helper/index.d.ts +4 -0
- package/dist/schema-helper/index.d.ts.map +1 -0
- package/dist/schema-helper/index.js +16 -0
- package/dist/schema-helper/index.js.map +1 -0
- package/dist/schema-helper/resolverMap.d.ts +16 -0
- package/dist/schema-helper/resolverMap.d.ts.map +1 -0
- package/dist/schema-helper/resolverMap.js +3 -0
- package/dist/schema-helper/resolverMap.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +31 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js +112 -0
- package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +12 -0
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -0
- package/dist/{loadServicesFromRemoteEndpoint.js → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js} +6 -6
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts +33 -0
- package/dist/supergraphManagers/LegacyFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js +149 -0
- package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts +19 -0
- package/dist/supergraphManagers/LocalCompose/index.d.ts.map +1 -0
- package/dist/supergraphManagers/LocalCompose/index.js +55 -0
- package/dist/supergraphManagers/LocalCompose/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +32 -0
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js +96 -0
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +21 -0
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/{loadSupergraphSdlFromStorage.js → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js} +41 -10
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts +13 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js +85 -0
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +1 -0
- package/dist/supergraphManagers/index.d.ts +5 -0
- package/dist/supergraphManagers/index.d.ts.map +1 -0
- package/dist/supergraphManagers/index.js +12 -0
- package/dist/supergraphManagers/index.js.map +1 -0
- package/dist/utilities/array.js +1 -1
- package/dist/utilities/array.js.map +1 -1
- package/dist/utilities/createHash.d.ts +2 -0
- package/dist/utilities/createHash.d.ts.map +1 -0
- package/dist/utilities/createHash.js +15 -0
- package/dist/utilities/createHash.js.map +1 -0
- package/dist/utilities/isNodeLike.d.ts +3 -0
- package/dist/utilities/isNodeLike.d.ts.map +1 -0
- package/dist/utilities/isNodeLike.js +8 -0
- package/dist/utilities/isNodeLike.js.map +1 -0
- package/package.json +9 -9
- package/src/__generated__/graphqlTypes.ts +13 -11
- package/src/__mocks__/make-fetch-happen-fetcher.ts +3 -1
- package/src/__tests__/buildQueryPlan.test.ts +1 -1
- package/src/__tests__/executeQueryPlan.test.ts +1171 -77
- package/src/__tests__/execution-utils.ts +5 -7
- package/src/__tests__/gateway/buildService.test.ts +3 -3
- package/src/__tests__/gateway/endToEnd.test.ts +1 -1
- package/src/__tests__/gateway/executor.test.ts +3 -1
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +59 -121
- package/src/__tests__/gateway/opentelemetry.test.ts +8 -3
- package/src/__tests__/gateway/queryPlanCache.test.ts +25 -9
- package/src/__tests__/gateway/reporting.test.ts +42 -13
- package/src/__tests__/gateway/supergraphSdl.test.ts +397 -0
- package/src/__tests__/integration/aliases.test.ts +9 -3
- package/src/__tests__/integration/configuration.test.ts +140 -21
- package/src/__tests__/integration/logger.test.ts +2 -2
- package/src/__tests__/integration/networkRequests.test.ts +126 -149
- package/src/__tests__/integration/nockMocks.ts +57 -16
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +153 -77
- package/src/core/__tests__/core.test.ts +6 -6
- package/src/datasources/LocalGraphQLDataSource.ts +1 -1
- package/src/datasources/RemoteGraphQLDataSource.ts +8 -2
- package/src/datasources/__tests__/LocalGraphQLDataSource.test.ts +1 -1
- package/src/datasources/__tests__/RemoteGraphQLDataSource.test.ts +4 -4
- package/src/datasources/types.ts +1 -1
- package/src/executeQueryPlan.ts +18 -9
- package/src/index.ts +323 -481
- package/src/make-fetch-happen.d.ts +1 -1
- package/src/operationContext.ts +2 -2
- package/src/schema-helper/addResolversToSchema.ts +83 -0
- package/src/schema-helper/error.ts +11 -0
- package/src/schema-helper/index.ts +3 -0
- package/src/schema-helper/resolverMap.ts +23 -0
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/IntrospectAndCompose.test.ts +370 -0
- package/src/{__tests__ → supergraphManagers/IntrospectAndCompose/__tests__}/loadServicesFromRemoteEndpoint.test.ts +7 -7
- package/src/supergraphManagers/IntrospectAndCompose/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/IntrospectAndCompose/index.ts +160 -0
- package/src/{loadServicesFromRemoteEndpoint.ts → supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts} +7 -7
- package/src/supergraphManagers/LegacyFetcher/index.ts +226 -0
- package/src/supergraphManagers/LocalCompose/index.ts +79 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +343 -0
- package/src/supergraphManagers/UplinkFetcher/__tests__/tsconfig.json +8 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +128 -0
- package/src/{loadSupergraphSdlFromStorage.ts → supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts} +63 -14
- package/src/supergraphManagers/UplinkFetcher/outOfBandReporter.ts +126 -0
- package/src/supergraphManagers/index.ts +4 -0
- package/src/utilities/__tests__/cleanErrorOfInaccessibleElements.test.ts +13 -10
- package/src/utilities/array.ts +1 -1
- package/src/utilities/createHash.ts +10 -0
- package/src/utilities/isNodeLike.ts +11 -0
- package/CHANGELOG.md +0 -452
- package/dist/legacyLoadServicesFromStorage.d.ts +0 -20
- package/dist/legacyLoadServicesFromStorage.d.ts.map +0 -1
- package/dist/legacyLoadServicesFromStorage.js +0 -62
- package/dist/legacyLoadServicesFromStorage.js.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.d.ts +0 -13
- package/dist/loadServicesFromRemoteEndpoint.d.ts.map +0 -1
- package/dist/loadServicesFromRemoteEndpoint.js.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +0 -12
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/outOfBandReporter.d.ts +0 -15
- package/dist/outOfBandReporter.d.ts.map +0 -1
- package/dist/outOfBandReporter.js +0 -88
- package/dist/outOfBandReporter.js.map +0 -1
- package/src/__tests__/gateway/composedSdl.test.ts +0 -44
- package/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
- package/src/__tests__/integration/legacyNockMocks.ts +0 -113
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +0 -664
- package/src/legacyLoadServicesFromStorage.ts +0 -170
- package/src/outOfBandReporter.ts +0 -128
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadSupergraphSdlFromStorage,
|
|
3
|
+
loadSupergraphSdlFromUplinks
|
|
4
|
+
} from '../loadSupergraphSdlFromStorage';
|
|
5
|
+
import { getDefaultFetcher } from '../../..';
|
|
6
|
+
import {
|
|
7
|
+
graphRef,
|
|
8
|
+
apiKey,
|
|
9
|
+
mockCloudConfigUrl1,
|
|
10
|
+
mockCloudConfigUrl2,
|
|
11
|
+
mockOutOfBandReporterUrl,
|
|
12
|
+
mockSupergraphSdlRequest,
|
|
13
|
+
mockOutOfBandReportRequestSuccess,
|
|
14
|
+
mockSupergraphSdlRequestSuccess,
|
|
15
|
+
mockSupergraphSdlRequestIfAfterUnchanged,
|
|
16
|
+
mockSupergraphSdlRequestIfAfter
|
|
17
|
+
} from '../../../__tests__/integration/nockMocks';
|
|
18
|
+
import { getTestingSupergraphSdl } from "../../../__tests__/execution-utils";
|
|
19
|
+
import { nockAfterEach, nockBeforeEach } from '../../../__tests__/nockAssertions';
|
|
20
|
+
|
|
21
|
+
describe('loadSupergraphSdlFromStorage', () => {
|
|
22
|
+
beforeEach(nockBeforeEach);
|
|
23
|
+
afterEach(nockAfterEach);
|
|
24
|
+
|
|
25
|
+
it('fetches Supergraph SDL as expected', async () => {
|
|
26
|
+
mockSupergraphSdlRequestSuccess();
|
|
27
|
+
const fetcher = getDefaultFetcher();
|
|
28
|
+
const result = await loadSupergraphSdlFromStorage({
|
|
29
|
+
graphRef,
|
|
30
|
+
apiKey,
|
|
31
|
+
endpoint: mockCloudConfigUrl1,
|
|
32
|
+
errorReportingEndpoint: undefined,
|
|
33
|
+
fetcher,
|
|
34
|
+
compositionId: null,
|
|
35
|
+
});
|
|
36
|
+
expect(result).toMatchObject({
|
|
37
|
+
id: 'originalId-1234',
|
|
38
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('Queries alternate Uplink URL if first one fails', async () => {
|
|
43
|
+
mockSupergraphSdlRequest('originalId-1234', mockCloudConfigUrl1).reply(500);
|
|
44
|
+
mockSupergraphSdlRequestIfAfter('originalId-1234', mockCloudConfigUrl2).reply(
|
|
45
|
+
200,
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
data: {
|
|
48
|
+
routerConfig: {
|
|
49
|
+
__typename: 'RouterConfigResult',
|
|
50
|
+
id: 'originalId-1234',
|
|
51
|
+
supergraphSdl: getTestingSupergraphSdl()
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const fetcher = getDefaultFetcher();
|
|
58
|
+
const result = await loadSupergraphSdlFromUplinks({
|
|
59
|
+
graphRef,
|
|
60
|
+
apiKey,
|
|
61
|
+
endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
|
|
62
|
+
errorReportingEndpoint: undefined,
|
|
63
|
+
fetcher,
|
|
64
|
+
compositionId: "originalId-1234",
|
|
65
|
+
maxRetries: 1
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result).toMatchObject({
|
|
69
|
+
id: 'originalId-1234',
|
|
70
|
+
supergraphSdl: getTestingSupergraphSdl(),
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('Throws error if all Uplink URLs fail', async () => {
|
|
75
|
+
mockSupergraphSdlRequest("originalId-1234", mockCloudConfigUrl1).reply(500);
|
|
76
|
+
mockSupergraphSdlRequestIfAfter("originalId-1234", mockCloudConfigUrl2).reply(500);
|
|
77
|
+
|
|
78
|
+
const fetcher = getDefaultFetcher();
|
|
79
|
+
await expect(
|
|
80
|
+
loadSupergraphSdlFromUplinks({
|
|
81
|
+
graphRef,
|
|
82
|
+
apiKey,
|
|
83
|
+
endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
|
|
84
|
+
errorReportingEndpoint: undefined,
|
|
85
|
+
fetcher,
|
|
86
|
+
compositionId: "originalId-1234",
|
|
87
|
+
maxRetries: 1
|
|
88
|
+
}),
|
|
89
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
90
|
+
`"An error occurred while fetching your schema from Apollo: 500 Internal Server Error"`,
|
|
91
|
+
);
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('errors', () => {
|
|
95
|
+
it('throws on a malformed response', async () => {
|
|
96
|
+
mockSupergraphSdlRequest().reply(200, 'Invalid JSON');
|
|
97
|
+
|
|
98
|
+
const fetcher = getDefaultFetcher();
|
|
99
|
+
await expect(
|
|
100
|
+
loadSupergraphSdlFromStorage({
|
|
101
|
+
graphRef,
|
|
102
|
+
apiKey,
|
|
103
|
+
endpoint: mockCloudConfigUrl1,
|
|
104
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
105
|
+
fetcher,
|
|
106
|
+
compositionId: null,
|
|
107
|
+
}),
|
|
108
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
109
|
+
`"An error occurred while fetching your schema from Apollo: 200 invalid json response body at https://example1.cloud-config-url.com/cloudconfig/ reason: Unexpected token I in JSON at position 0"`,
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('throws errors from JSON on 400', async () => {
|
|
114
|
+
const message = 'Query syntax error';
|
|
115
|
+
mockSupergraphSdlRequest().reply(
|
|
116
|
+
400,
|
|
117
|
+
JSON.stringify({
|
|
118
|
+
errors: [{ message }],
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const fetcher = getDefaultFetcher();
|
|
123
|
+
await expect(
|
|
124
|
+
loadSupergraphSdlFromStorage({
|
|
125
|
+
graphRef,
|
|
126
|
+
apiKey,
|
|
127
|
+
endpoint: mockCloudConfigUrl1,
|
|
128
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
129
|
+
fetcher,
|
|
130
|
+
compositionId: null,
|
|
131
|
+
}),
|
|
132
|
+
).rejects.toThrowError(message);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("throws on non-OK status codes when `errors` isn't present in a JSON response", async () => {
|
|
136
|
+
mockSupergraphSdlRequest().reply(500);
|
|
137
|
+
mockOutOfBandReportRequestSuccess();
|
|
138
|
+
|
|
139
|
+
const fetcher = getDefaultFetcher();
|
|
140
|
+
await expect(
|
|
141
|
+
loadSupergraphSdlFromStorage({
|
|
142
|
+
graphRef,
|
|
143
|
+
apiKey,
|
|
144
|
+
endpoint: mockCloudConfigUrl1,
|
|
145
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
146
|
+
fetcher,
|
|
147
|
+
compositionId: null,
|
|
148
|
+
}),
|
|
149
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
150
|
+
`"An error occurred while fetching your schema from Apollo: 500 Internal Server Error"`,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// if an additional request were made by the out of band reporter, nock would throw since it's unmocked
|
|
155
|
+
// and this test would fail
|
|
156
|
+
it("Out of band reporting doesn't submit reports when endpoint is not configured", async () => {
|
|
157
|
+
mockSupergraphSdlRequest().reply(400);
|
|
158
|
+
|
|
159
|
+
const fetcher = getDefaultFetcher();
|
|
160
|
+
await expect(
|
|
161
|
+
loadSupergraphSdlFromStorage({
|
|
162
|
+
graphRef,
|
|
163
|
+
apiKey,
|
|
164
|
+
endpoint: mockCloudConfigUrl1,
|
|
165
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
166
|
+
fetcher,
|
|
167
|
+
compositionId: null,
|
|
168
|
+
}),
|
|
169
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
170
|
+
`"An error occurred while fetching your schema from Apollo: 400 invalid json response body at https://example1.cloud-config-url.com/cloudconfig/ reason: Unexpected end of JSON input"`,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('throws on 400 status response and does not submit an out of band error', async () => {
|
|
175
|
+
|
|
176
|
+
mockSupergraphSdlRequest().reply(400);
|
|
177
|
+
|
|
178
|
+
const fetcher = getDefaultFetcher();
|
|
179
|
+
await expect(
|
|
180
|
+
loadSupergraphSdlFromStorage({
|
|
181
|
+
graphRef,
|
|
182
|
+
apiKey,
|
|
183
|
+
endpoint: mockCloudConfigUrl1,
|
|
184
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
185
|
+
fetcher,
|
|
186
|
+
compositionId: null,
|
|
187
|
+
}),
|
|
188
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
189
|
+
`"An error occurred while fetching your schema from Apollo: 400 invalid json response body at https://example1.cloud-config-url.com/cloudconfig/ reason: Unexpected end of JSON input"`,
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('throws on 413 status response and successfully submits an out of band error', async () => {
|
|
194
|
+
mockSupergraphSdlRequest().reply(413);
|
|
195
|
+
mockOutOfBandReportRequestSuccess();
|
|
196
|
+
|
|
197
|
+
const fetcher = getDefaultFetcher();
|
|
198
|
+
await expect(
|
|
199
|
+
loadSupergraphSdlFromStorage({
|
|
200
|
+
graphRef,
|
|
201
|
+
apiKey,
|
|
202
|
+
endpoint: mockCloudConfigUrl1,
|
|
203
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
204
|
+
fetcher,
|
|
205
|
+
compositionId: null,
|
|
206
|
+
}),
|
|
207
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
208
|
+
`"An error occurred while fetching your schema from Apollo: 413 Payload Too Large"`,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('throws on 422 status response and successfully submits an out of band error', async () => {
|
|
213
|
+
mockSupergraphSdlRequest().reply(422);
|
|
214
|
+
mockOutOfBandReportRequestSuccess();
|
|
215
|
+
|
|
216
|
+
const fetcher = getDefaultFetcher();
|
|
217
|
+
await expect(
|
|
218
|
+
loadSupergraphSdlFromStorage({
|
|
219
|
+
graphRef,
|
|
220
|
+
apiKey,
|
|
221
|
+
endpoint: mockCloudConfigUrl1,
|
|
222
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
223
|
+
fetcher,
|
|
224
|
+
compositionId: null,
|
|
225
|
+
}),
|
|
226
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
227
|
+
`"An error occurred while fetching your schema from Apollo: 422 Unprocessable Entity"`,
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('throws on 408 status response and successfully submits an out of band error', async () => {
|
|
232
|
+
mockSupergraphSdlRequest().reply(408);
|
|
233
|
+
mockOutOfBandReportRequestSuccess();
|
|
234
|
+
|
|
235
|
+
const fetcher = getDefaultFetcher();
|
|
236
|
+
await expect(
|
|
237
|
+
loadSupergraphSdlFromStorage({
|
|
238
|
+
graphRef,
|
|
239
|
+
apiKey,
|
|
240
|
+
endpoint: mockCloudConfigUrl1,
|
|
241
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
242
|
+
fetcher,
|
|
243
|
+
compositionId: null,
|
|
244
|
+
}),
|
|
245
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
246
|
+
`"An error occurred while fetching your schema from Apollo: 408 Request Timeout"`,
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('throws on 504 status response and successfully submits an out of band error', async () => {
|
|
252
|
+
|
|
253
|
+
mockSupergraphSdlRequest().reply(504);
|
|
254
|
+
mockOutOfBandReportRequestSuccess();
|
|
255
|
+
|
|
256
|
+
const fetcher = getDefaultFetcher();
|
|
257
|
+
await expect(
|
|
258
|
+
loadSupergraphSdlFromStorage({
|
|
259
|
+
graphRef,
|
|
260
|
+
apiKey,
|
|
261
|
+
endpoint: mockCloudConfigUrl1,
|
|
262
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
263
|
+
fetcher,
|
|
264
|
+
compositionId: null,
|
|
265
|
+
}),
|
|
266
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
267
|
+
`"An error occurred while fetching your schema from Apollo: 504 Gateway Timeout"`,
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('throws when there is no response and successfully submits an out of band error', async () => {
|
|
272
|
+
mockSupergraphSdlRequest().replyWithError('no response');
|
|
273
|
+
mockOutOfBandReportRequestSuccess();
|
|
274
|
+
|
|
275
|
+
const fetcher = getDefaultFetcher();
|
|
276
|
+
await expect(
|
|
277
|
+
loadSupergraphSdlFromStorage({
|
|
278
|
+
graphRef,
|
|
279
|
+
apiKey,
|
|
280
|
+
endpoint: mockCloudConfigUrl1,
|
|
281
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
282
|
+
fetcher,
|
|
283
|
+
compositionId: null,
|
|
284
|
+
}),
|
|
285
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
286
|
+
`"An error occurred while fetching your schema from Apollo: request to https://example1.cloud-config-url.com/cloudconfig/ failed, reason: no response"`,
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('throws on 502 status response and successfully submits an out of band error', async () => {
|
|
291
|
+
mockSupergraphSdlRequest().reply(502);
|
|
292
|
+
mockOutOfBandReportRequestSuccess();
|
|
293
|
+
|
|
294
|
+
const fetcher = getDefaultFetcher();
|
|
295
|
+
await expect(
|
|
296
|
+
loadSupergraphSdlFromStorage({
|
|
297
|
+
graphRef,
|
|
298
|
+
apiKey,
|
|
299
|
+
endpoint: mockCloudConfigUrl1,
|
|
300
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
301
|
+
fetcher,
|
|
302
|
+
compositionId: null,
|
|
303
|
+
}),
|
|
304
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
305
|
+
`"An error occurred while fetching your schema from Apollo: 502 Bad Gateway"`,
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('throws on 503 status response and successfully submits an out of band error', async () => {
|
|
310
|
+
mockSupergraphSdlRequest().reply(503);
|
|
311
|
+
mockOutOfBandReportRequestSuccess();
|
|
312
|
+
|
|
313
|
+
const fetcher = getDefaultFetcher();
|
|
314
|
+
await expect(
|
|
315
|
+
loadSupergraphSdlFromStorage({
|
|
316
|
+
graphRef,
|
|
317
|
+
apiKey,
|
|
318
|
+
endpoint: mockCloudConfigUrl1,
|
|
319
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
320
|
+
fetcher,
|
|
321
|
+
compositionId: null,
|
|
322
|
+
}),
|
|
323
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
324
|
+
`"An error occurred while fetching your schema from Apollo: 503 Service Unavailable"`,
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('successfully responds to SDL unchanged by returning null', async () => {
|
|
329
|
+
mockSupergraphSdlRequestIfAfterUnchanged("id-1234");
|
|
330
|
+
|
|
331
|
+
const fetcher = getDefaultFetcher();
|
|
332
|
+
const result = await loadSupergraphSdlFromStorage({
|
|
333
|
+
graphRef,
|
|
334
|
+
apiKey,
|
|
335
|
+
endpoint: mockCloudConfigUrl1,
|
|
336
|
+
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
337
|
+
fetcher,
|
|
338
|
+
compositionId: "id-1234",
|
|
339
|
+
});
|
|
340
|
+
expect(result).toBeNull();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
@@ -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,12 +1,13 @@
|
|
|
1
1
|
import { fetch, Response, Request } from 'apollo-server-env';
|
|
2
2
|
import { GraphQLError } from 'graphql';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { SupergraphSdlUpdate } from '../../config';
|
|
4
|
+
import { submitOutOfBandReportIfConfigured } from './outOfBandReporter';
|
|
5
|
+
import { SupergraphSdlQuery } from '../../__generated__/graphqlTypes';
|
|
5
6
|
|
|
6
7
|
// Magic /* GraphQL */ comment below is for codegen, do not remove
|
|
7
8
|
export const SUPERGRAPH_SDL_QUERY = /* GraphQL */`#graphql
|
|
8
|
-
query SupergraphSdl($apiKey: String!, $ref: String
|
|
9
|
-
routerConfig(ref: $ref, apiKey: $apiKey) {
|
|
9
|
+
query SupergraphSdl($apiKey: String!, $ref: String!, $ifAfterId: ID) {
|
|
10
|
+
routerConfig(ref: $ref, apiKey: $apiKey, ifAfterId: $ifAfterId) {
|
|
10
11
|
__typename
|
|
11
12
|
... on RouterConfigResult {
|
|
12
13
|
id
|
|
@@ -34,21 +35,67 @@ interface SupergraphSdlQueryFailure {
|
|
|
34
35
|
errors: GraphQLError[];
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
const { name, version } = require('
|
|
38
|
+
const { name, version } = require('../../../package.json');
|
|
38
39
|
|
|
39
40
|
const fetchErrorMsg = "An error occurred while fetching your schema from Apollo: ";
|
|
40
41
|
|
|
42
|
+
let fetchCounter = 0;
|
|
43
|
+
|
|
44
|
+
export async function loadSupergraphSdlFromUplinks({
|
|
45
|
+
graphRef,
|
|
46
|
+
apiKey,
|
|
47
|
+
endpoints,
|
|
48
|
+
errorReportingEndpoint,
|
|
49
|
+
fetcher,
|
|
50
|
+
compositionId,
|
|
51
|
+
maxRetries,
|
|
52
|
+
}: {
|
|
53
|
+
graphRef: string;
|
|
54
|
+
apiKey: string;
|
|
55
|
+
endpoints: string[];
|
|
56
|
+
errorReportingEndpoint: string | undefined,
|
|
57
|
+
fetcher: typeof fetch;
|
|
58
|
+
compositionId: string | null;
|
|
59
|
+
maxRetries: number
|
|
60
|
+
}) : Promise<SupergraphSdlUpdate | null> {
|
|
61
|
+
let retries = 0;
|
|
62
|
+
let lastException = null;
|
|
63
|
+
let result: SupergraphSdlUpdate | null = null;
|
|
64
|
+
while (retries++ <= maxRetries && result == null) {
|
|
65
|
+
try {
|
|
66
|
+
result = await loadSupergraphSdlFromStorage({
|
|
67
|
+
graphRef,
|
|
68
|
+
apiKey,
|
|
69
|
+
endpoint: endpoints[fetchCounter++ % endpoints.length],
|
|
70
|
+
errorReportingEndpoint,
|
|
71
|
+
fetcher,
|
|
72
|
+
compositionId
|
|
73
|
+
});
|
|
74
|
+
} catch (e) {
|
|
75
|
+
lastException = e;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (result === null && lastException !== null) {
|
|
79
|
+
throw lastException;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
41
84
|
export async function loadSupergraphSdlFromStorage({
|
|
42
85
|
graphRef,
|
|
43
86
|
apiKey,
|
|
44
87
|
endpoint,
|
|
88
|
+
errorReportingEndpoint,
|
|
45
89
|
fetcher,
|
|
90
|
+
compositionId,
|
|
46
91
|
}: {
|
|
47
92
|
graphRef: string;
|
|
48
93
|
apiKey: string;
|
|
49
94
|
endpoint: string;
|
|
95
|
+
errorReportingEndpoint?: string;
|
|
50
96
|
fetcher: typeof fetch;
|
|
51
|
-
|
|
97
|
+
compositionId: string | null;
|
|
98
|
+
}) : Promise<SupergraphSdlUpdate | null> {
|
|
52
99
|
let result: Response;
|
|
53
100
|
const requestDetails = {
|
|
54
101
|
method: 'POST',
|
|
@@ -57,6 +104,7 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
57
104
|
variables: {
|
|
58
105
|
ref: graphRef,
|
|
59
106
|
apiKey,
|
|
107
|
+
ifAfterId: compositionId,
|
|
60
108
|
},
|
|
61
109
|
}),
|
|
62
110
|
headers: {
|
|
@@ -69,19 +117,19 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
69
117
|
|
|
70
118
|
const request: Request = new Request(endpoint, requestDetails);
|
|
71
119
|
|
|
72
|
-
const
|
|
73
|
-
const startTime = new Date()
|
|
120
|
+
const startTime = new Date();
|
|
74
121
|
try {
|
|
75
122
|
result = await fetcher(endpoint, requestDetails);
|
|
76
123
|
} catch (e) {
|
|
77
124
|
const endTime = new Date();
|
|
78
125
|
|
|
79
|
-
await
|
|
126
|
+
await submitOutOfBandReportIfConfigured({
|
|
80
127
|
error: e,
|
|
81
128
|
request,
|
|
129
|
+
endpoint: errorReportingEndpoint,
|
|
82
130
|
startedAt: startTime,
|
|
83
131
|
endedAt: endTime,
|
|
84
|
-
fetcher
|
|
132
|
+
fetcher,
|
|
85
133
|
});
|
|
86
134
|
|
|
87
135
|
throw new Error(fetchErrorMsg + (e.message ?? e));
|
|
@@ -106,13 +154,14 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
106
154
|
);
|
|
107
155
|
}
|
|
108
156
|
} else {
|
|
109
|
-
await
|
|
157
|
+
await submitOutOfBandReportIfConfigured({
|
|
110
158
|
error: new Error(fetchErrorMsg + result.status + ' ' + result.statusText),
|
|
111
159
|
request,
|
|
160
|
+
endpoint: errorReportingEndpoint,
|
|
112
161
|
response: result,
|
|
113
162
|
startedAt: startTime,
|
|
114
163
|
endedAt: endTime,
|
|
115
|
-
fetcher
|
|
164
|
+
fetcher,
|
|
116
165
|
});
|
|
117
166
|
throw new Error(fetchErrorMsg + result.status + ' ' + result.statusText);
|
|
118
167
|
}
|
|
@@ -124,13 +173,13 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
124
173
|
supergraphSdl,
|
|
125
174
|
// messages,
|
|
126
175
|
} = routerConfig;
|
|
127
|
-
|
|
128
|
-
// `supergraphSdl` should not be nullable in the schema, but it currently is
|
|
129
176
|
return { id, supergraphSdl: supergraphSdl! };
|
|
130
177
|
} else if (routerConfig.__typename === 'FetchError') {
|
|
131
178
|
// FetchError case
|
|
132
179
|
const { code, message } = routerConfig;
|
|
133
180
|
throw new Error(`${code}: ${message}`);
|
|
181
|
+
} else if (routerConfig.__typename === 'Unchanged') {
|
|
182
|
+
return null;
|
|
134
183
|
} else {
|
|
135
184
|
throw new Error('Programming error: unhandled response failure');
|
|
136
185
|
}
|