@apollo/gateway 2.0.6-rc.0 → 2.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.d.ts.map +1 -1
- package/dist/datasources/RemoteGraphQLDataSource.js +2 -3
- package/dist/datasources/RemoteGraphQLDataSource.js.map +1 -1
- package/dist/datasources/types.d.ts +1 -0
- package/dist/datasources/types.d.ts.map +1 -1
- package/dist/executeQueryPlan.d.ts +2 -2
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +24 -16
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -62
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/operationContext.d.ts.map +1 -1
- package/dist/operationContext.js +3 -7
- package/dist/operationContext.js.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/index.d.ts.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.d.ts.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.js.map +1 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/index.d.ts +61 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/index.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/index.js +209 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/index.js.map +1 -0
- package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/loadSupergraphSdlFromStorage.d.ts +8 -4
- package/dist/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.d.ts.map +1 -0
- package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/loadSupergraphSdlFromStorage.js +20 -8
- package/dist/supergraphManagers/UplinkSupergraphManager/loadSupergraphSdlFromStorage.js.map +1 -0
- package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.d.ts +2 -1
- package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.d.ts.map +1 -0
- package/dist/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.js +0 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/outOfBandReporter.js.map +1 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/types.d.ts +9 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/types.d.ts.map +1 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/types.js +5 -0
- package/dist/supergraphManagers/UplinkSupergraphManager/types.js.map +1 -0
- package/dist/supergraphManagers/index.d.ts +2 -2
- package/dist/supergraphManagers/index.d.ts.map +1 -1
- package/dist/supergraphManagers/index.js +17 -4
- package/dist/supergraphManagers/index.js.map +1 -1
- package/package.json +10 -10
- package/src/__tests__/executeQueryPlan.test.ts +199 -1
- package/src/__tests__/execution-utils.ts +5 -3
- package/src/__tests__/integration/abstract-types.test.ts +31 -65
- package/src/__tests__/integration/configuration.test.ts +2 -45
- package/src/__tests__/integration/managed.test.ts +292 -0
- package/src/__tests__/integration/networkRequests.test.ts +14 -54
- package/src/__tests__/integration/nockMocks.ts +7 -6
- package/src/config.ts +3 -1
- package/src/datasources/RemoteGraphQLDataSource.ts +1 -2
- package/src/datasources/types.ts +4 -0
- package/src/executeQueryPlan.ts +41 -30
- package/src/index.ts +33 -88
- package/src/logger.ts +11 -0
- package/src/operationContext.ts +5 -7
- package/src/supergraphManagers/IntrospectAndCompose/index.ts +1 -1
- package/src/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint.ts +1 -1
- package/src/supergraphManagers/UplinkSupergraphManager/__tests__/UplinkSupergraphManager.test.ts +65 -0
- package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/__tests__/loadSupergraphSdlFromStorage.test.ts +51 -16
- package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/__tests__/tsconfig.json +0 -0
- package/src/supergraphManagers/UplinkSupergraphManager/index.ts +312 -0
- package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/loadSupergraphSdlFromStorage.ts +32 -12
- package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.ts +2 -1
- package/src/supergraphManagers/UplinkSupergraphManager/types.ts +10 -0
- package/src/supergraphManagers/index.ts +2 -2
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +0 -35
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +0 -1
- package/dist/supergraphManagers/UplinkFetcher/index.js +0 -114
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +0 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +0 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +0 -1
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.d.ts.map +0 -1
- package/dist/supergraphManagers/UplinkFetcher/outOfBandReporter.js.map +0 -1
- package/src/supergraphManagers/UplinkFetcher/index.ts +0 -149
|
@@ -22,6 +22,15 @@ import {
|
|
|
22
22
|
nockBeforeEach,
|
|
23
23
|
} from '../../../__tests__/nockAssertions';
|
|
24
24
|
|
|
25
|
+
const logger = {
|
|
26
|
+
warn: jest.fn(),
|
|
27
|
+
debug: jest.fn(),
|
|
28
|
+
error: jest.fn(),
|
|
29
|
+
info: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const requestTimeoutMs = 100;
|
|
33
|
+
|
|
25
34
|
describe('loadSupergraphSdlFromStorage', () => {
|
|
26
35
|
beforeEach(nockBeforeEach);
|
|
27
36
|
afterEach(nockAfterEach);
|
|
@@ -34,7 +43,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
34
43
|
endpoint: mockCloudConfigUrl1,
|
|
35
44
|
errorReportingEndpoint: undefined,
|
|
36
45
|
fetcher,
|
|
46
|
+
requestTimeoutMs,
|
|
37
47
|
compositionId: null,
|
|
48
|
+
logger,
|
|
38
49
|
});
|
|
39
50
|
expect(result).toMatchObject({
|
|
40
51
|
id: 'originalId-1234',
|
|
@@ -66,10 +77,11 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
66
77
|
endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
|
|
67
78
|
errorReportingEndpoint: undefined,
|
|
68
79
|
fetcher,
|
|
80
|
+
requestTimeoutMs,
|
|
69
81
|
compositionId: 'originalId-1234',
|
|
70
82
|
maxRetries: 1,
|
|
71
83
|
roundRobinSeed: 0,
|
|
72
|
-
|
|
84
|
+
logger,
|
|
73
85
|
});
|
|
74
86
|
|
|
75
87
|
expect(result).toMatchObject({
|
|
@@ -92,10 +104,11 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
92
104
|
endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
|
|
93
105
|
errorReportingEndpoint: undefined,
|
|
94
106
|
fetcher,
|
|
107
|
+
requestTimeoutMs,
|
|
95
108
|
compositionId: 'originalId-1234',
|
|
96
109
|
maxRetries: 1,
|
|
97
110
|
roundRobinSeed: 0,
|
|
98
|
-
|
|
111
|
+
logger,
|
|
99
112
|
}),
|
|
100
113
|
).rejects.toThrowError(
|
|
101
114
|
new UplinkFetcherError(
|
|
@@ -115,7 +128,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
115
128
|
endpoint: mockCloudConfigUrl1,
|
|
116
129
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
117
130
|
fetcher,
|
|
131
|
+
requestTimeoutMs,
|
|
118
132
|
compositionId: null,
|
|
133
|
+
logger,
|
|
119
134
|
}),
|
|
120
135
|
).rejects.toThrowError(
|
|
121
136
|
new UplinkFetcherError(
|
|
@@ -140,7 +155,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
140
155
|
endpoint: mockCloudConfigUrl1,
|
|
141
156
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
142
157
|
fetcher,
|
|
158
|
+
requestTimeoutMs,
|
|
143
159
|
compositionId: null,
|
|
160
|
+
logger,
|
|
144
161
|
}),
|
|
145
162
|
).rejects.toThrowError(
|
|
146
163
|
new UplinkFetcherError(
|
|
@@ -160,7 +177,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
160
177
|
endpoint: mockCloudConfigUrl1,
|
|
161
178
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
162
179
|
fetcher,
|
|
180
|
+
requestTimeoutMs,
|
|
163
181
|
compositionId: null,
|
|
182
|
+
logger,
|
|
164
183
|
}),
|
|
165
184
|
).rejects.toThrowError(
|
|
166
185
|
new UplinkFetcherError(
|
|
@@ -181,7 +200,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
181
200
|
endpoint: mockCloudConfigUrl1,
|
|
182
201
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
183
202
|
fetcher,
|
|
203
|
+
requestTimeoutMs,
|
|
184
204
|
compositionId: null,
|
|
205
|
+
logger,
|
|
185
206
|
}),
|
|
186
207
|
).rejects.toThrowError(
|
|
187
208
|
new UplinkFetcherError(
|
|
@@ -200,7 +221,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
200
221
|
endpoint: mockCloudConfigUrl1,
|
|
201
222
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
202
223
|
fetcher,
|
|
224
|
+
requestTimeoutMs,
|
|
203
225
|
compositionId: null,
|
|
226
|
+
logger,
|
|
204
227
|
}),
|
|
205
228
|
).rejects.toThrowError(
|
|
206
229
|
new UplinkFetcherError(
|
|
@@ -220,7 +243,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
220
243
|
endpoint: mockCloudConfigUrl1,
|
|
221
244
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
222
245
|
fetcher,
|
|
246
|
+
requestTimeoutMs,
|
|
223
247
|
compositionId: null,
|
|
248
|
+
logger,
|
|
224
249
|
}),
|
|
225
250
|
).rejects.toThrowError(
|
|
226
251
|
new UplinkFetcherError(
|
|
@@ -240,7 +265,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
240
265
|
endpoint: mockCloudConfigUrl1,
|
|
241
266
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
242
267
|
fetcher,
|
|
268
|
+
requestTimeoutMs,
|
|
243
269
|
compositionId: null,
|
|
270
|
+
logger,
|
|
244
271
|
}),
|
|
245
272
|
).rejects.toThrowError(
|
|
246
273
|
new UplinkFetcherError(
|
|
@@ -260,7 +287,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
260
287
|
endpoint: mockCloudConfigUrl1,
|
|
261
288
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
262
289
|
fetcher,
|
|
290
|
+
requestTimeoutMs,
|
|
263
291
|
compositionId: null,
|
|
292
|
+
logger,
|
|
264
293
|
}),
|
|
265
294
|
).rejects.toThrowError(
|
|
266
295
|
new UplinkFetcherError(
|
|
@@ -281,7 +310,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
281
310
|
endpoint: mockCloudConfigUrl1,
|
|
282
311
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
283
312
|
fetcher,
|
|
313
|
+
requestTimeoutMs,
|
|
284
314
|
compositionId: null,
|
|
315
|
+
logger,
|
|
285
316
|
}),
|
|
286
317
|
).rejects.toThrowError(
|
|
287
318
|
new UplinkFetcherError(
|
|
@@ -301,7 +332,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
301
332
|
endpoint: mockCloudConfigUrl1,
|
|
302
333
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
303
334
|
fetcher,
|
|
335
|
+
requestTimeoutMs,
|
|
304
336
|
compositionId: null,
|
|
337
|
+
logger,
|
|
305
338
|
}),
|
|
306
339
|
).rejects.toThrowError(
|
|
307
340
|
new UplinkFetcherError(
|
|
@@ -321,7 +354,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
321
354
|
endpoint: mockCloudConfigUrl1,
|
|
322
355
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
323
356
|
fetcher,
|
|
357
|
+
requestTimeoutMs,
|
|
324
358
|
compositionId: null,
|
|
359
|
+
logger,
|
|
325
360
|
}),
|
|
326
361
|
).rejects.toThrowError(
|
|
327
362
|
new UplinkFetcherError(
|
|
@@ -341,7 +376,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
341
376
|
endpoint: mockCloudConfigUrl1,
|
|
342
377
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
343
378
|
fetcher,
|
|
379
|
+
requestTimeoutMs,
|
|
344
380
|
compositionId: null,
|
|
381
|
+
logger,
|
|
345
382
|
}),
|
|
346
383
|
).rejects.toThrowError(
|
|
347
384
|
new UplinkFetcherError(
|
|
@@ -359,7 +396,9 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
359
396
|
endpoint: mockCloudConfigUrl1,
|
|
360
397
|
errorReportingEndpoint: mockOutOfBandReporterUrl,
|
|
361
398
|
fetcher,
|
|
399
|
+
requestTimeoutMs,
|
|
362
400
|
compositionId: 'id-1234',
|
|
401
|
+
logger,
|
|
363
402
|
});
|
|
364
403
|
expect(result).toBeNull();
|
|
365
404
|
});
|
|
@@ -382,20 +421,20 @@ describe('loadSupergraphSdlFromUplinks', () => {
|
|
|
382
421
|
calls++;
|
|
383
422
|
return fetcher(...args);
|
|
384
423
|
},
|
|
424
|
+
requestTimeoutMs,
|
|
385
425
|
compositionId: 'id-1234',
|
|
386
426
|
maxRetries: 5,
|
|
387
427
|
roundRobinSeed: 0,
|
|
388
|
-
|
|
428
|
+
logger,
|
|
389
429
|
});
|
|
390
430
|
|
|
391
431
|
expect(result).toBeNull();
|
|
392
432
|
expect(calls).toBe(1);
|
|
393
433
|
});
|
|
394
434
|
|
|
395
|
-
it('
|
|
396
|
-
const timeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
397
|
-
|
|
435
|
+
it('Retries on error', async () => {
|
|
398
436
|
mockSupergraphSdlRequest('originalId-1234', mockCloudConfigUrl1).reply(500);
|
|
437
|
+
const supergraphSdl = getTestingSupergraphSdl();
|
|
399
438
|
mockSupergraphSdlRequestIfAfter(
|
|
400
439
|
'originalId-1234',
|
|
401
440
|
mockCloudConfigUrl2,
|
|
@@ -406,29 +445,25 @@ describe('loadSupergraphSdlFromUplinks', () => {
|
|
|
406
445
|
routerConfig: {
|
|
407
446
|
__typename: 'RouterConfigResult',
|
|
408
447
|
id: 'originalId-1234',
|
|
409
|
-
supergraphSdl
|
|
448
|
+
supergraphSdl,
|
|
410
449
|
},
|
|
411
450
|
},
|
|
412
451
|
}),
|
|
413
452
|
);
|
|
414
453
|
|
|
415
|
-
await loadSupergraphSdlFromUplinks({
|
|
454
|
+
const result = await loadSupergraphSdlFromUplinks({
|
|
416
455
|
graphRef,
|
|
417
456
|
apiKey,
|
|
418
457
|
endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
|
|
419
458
|
errorReportingEndpoint: undefined,
|
|
420
|
-
fetcher
|
|
459
|
+
fetcher,
|
|
460
|
+
requestTimeoutMs,
|
|
421
461
|
compositionId: 'originalId-1234',
|
|
422
462
|
maxRetries: 1,
|
|
423
463
|
roundRobinSeed: 0,
|
|
424
|
-
|
|
464
|
+
logger,
|
|
425
465
|
});
|
|
426
466
|
|
|
427
|
-
|
|
428
|
-
const setTimeoutCall = timeoutSpy.mock.calls[1][1];
|
|
429
|
-
expect(setTimeoutCall).toBeLessThanOrEqual(1000);
|
|
430
|
-
expect(setTimeoutCall).toBeGreaterThanOrEqual(900);
|
|
431
|
-
|
|
432
|
-
timeoutSpy.mockRestore();
|
|
467
|
+
expect(result?.supergraphSdl).toEqual(supergraphSdl);
|
|
433
468
|
});
|
|
434
469
|
});
|
package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/__tests__/tsconfig.json
RENAMED
|
File without changes
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import * as makeFetchHappen from 'make-fetch-happen';
|
|
2
|
+
import type { Logger } from '@apollo/utils.logger';
|
|
3
|
+
import resolvable, { Resolvable } from '@josephg/resolvable';
|
|
4
|
+
import { SupergraphManager, SupergraphSdlHookOptions } from '../../config';
|
|
5
|
+
import {
|
|
6
|
+
SubgraphHealthCheckFunction,
|
|
7
|
+
SupergraphSdlUpdateFunction,
|
|
8
|
+
} from '../..';
|
|
9
|
+
import { getDefaultLogger } from '../../logger';
|
|
10
|
+
import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
|
|
11
|
+
import type { AbortableFetcher } from './types';
|
|
12
|
+
|
|
13
|
+
export type FailureToFetchSupergraphSdlFunctionParams = {
|
|
14
|
+
error: Error;
|
|
15
|
+
graphRef: string;
|
|
16
|
+
logger: Logger;
|
|
17
|
+
fetchCount: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type FailureToFetchSupergraphSdlDuringInit = ({
|
|
21
|
+
error,
|
|
22
|
+
graphRef,
|
|
23
|
+
logger,
|
|
24
|
+
fetchCount,
|
|
25
|
+
}: FailureToFetchSupergraphSdlFunctionParams) => Promise<string>;
|
|
26
|
+
|
|
27
|
+
export type FailureToFetchSupergraphSdlAfterInit = ({
|
|
28
|
+
error,
|
|
29
|
+
graphRef,
|
|
30
|
+
logger,
|
|
31
|
+
fetchCount,
|
|
32
|
+
mostRecentSuccessfulFetchAt,
|
|
33
|
+
}:
|
|
34
|
+
| FailureToFetchSupergraphSdlFunctionParams & {
|
|
35
|
+
mostRecentSuccessfulFetchAt?: Date;
|
|
36
|
+
}) => Promise<string | null>;
|
|
37
|
+
|
|
38
|
+
type State =
|
|
39
|
+
| { phase: 'constructed' }
|
|
40
|
+
| { phase: 'initialized' }
|
|
41
|
+
| {
|
|
42
|
+
phase: 'polling';
|
|
43
|
+
pollingPromise?: Promise<void>;
|
|
44
|
+
nextFetchPromise?: Resolvable<void>;
|
|
45
|
+
}
|
|
46
|
+
| { phase: 'stopped' };
|
|
47
|
+
|
|
48
|
+
export class UplinkSupergraphManager implements SupergraphManager {
|
|
49
|
+
public static readonly DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
50
|
+
public static readonly MIN_POLL_INTERVAL_MS = 10_000;
|
|
51
|
+
|
|
52
|
+
public static readonly DEFAULT_UPLINK_ENDPOINTS = [
|
|
53
|
+
'https://uplink.api.apollographql.com/',
|
|
54
|
+
'https://aws.uplink.api.apollographql.com/',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
public readonly uplinkEndpoints: string[] =
|
|
58
|
+
UplinkSupergraphManager.getUplinkEndpoints();
|
|
59
|
+
private apiKey: string;
|
|
60
|
+
private graphRef: string;
|
|
61
|
+
private fetcher: AbortableFetcher = makeFetchHappen.defaults();
|
|
62
|
+
private maxRetries: number;
|
|
63
|
+
private requestTimeoutMs: number =
|
|
64
|
+
UplinkSupergraphManager.DEFAULT_REQUEST_TIMEOUT_MS;
|
|
65
|
+
private initialMaxRetries: number;
|
|
66
|
+
private pollIntervalMs: number = UplinkSupergraphManager.MIN_POLL_INTERVAL_MS;
|
|
67
|
+
private logger: Logger;
|
|
68
|
+
private update?: SupergraphSdlUpdateFunction;
|
|
69
|
+
private shouldRunSubgraphHealthcheck: boolean = false;
|
|
70
|
+
private healthCheck?: SubgraphHealthCheckFunction;
|
|
71
|
+
private onFailureToFetchSupergraphSdlDuringInit?: FailureToFetchSupergraphSdlDuringInit;
|
|
72
|
+
private onFailureToFetchSupergraphSdlAfterInit?: FailureToFetchSupergraphSdlAfterInit;
|
|
73
|
+
private timerRef: NodeJS.Timeout | null = null;
|
|
74
|
+
private state: State;
|
|
75
|
+
private errorReportingEndpoint: string | undefined =
|
|
76
|
+
process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
|
|
77
|
+
private compositionId?: string;
|
|
78
|
+
private fetchCount: number = 0;
|
|
79
|
+
private mostRecentSuccessfulFetchAt?: Date;
|
|
80
|
+
|
|
81
|
+
constructor({
|
|
82
|
+
apiKey,
|
|
83
|
+
graphRef,
|
|
84
|
+
debug,
|
|
85
|
+
logger,
|
|
86
|
+
uplinkEndpoints,
|
|
87
|
+
fallbackPollIntervalInMs,
|
|
88
|
+
maxRetries,
|
|
89
|
+
initialMaxRetries,
|
|
90
|
+
shouldRunSubgraphHealthcheck,
|
|
91
|
+
onFailureToFetchSupergraphSdlDuringInit,
|
|
92
|
+
onFailureToFetchSupergraphSdlAfterInit,
|
|
93
|
+
}: {
|
|
94
|
+
apiKey: string;
|
|
95
|
+
graphRef: string;
|
|
96
|
+
debug?: boolean;
|
|
97
|
+
logger?: Logger;
|
|
98
|
+
uplinkEndpoints?: string[];
|
|
99
|
+
fallbackPollIntervalInMs?: number;
|
|
100
|
+
maxRetries?: number;
|
|
101
|
+
initialMaxRetries?: number;
|
|
102
|
+
shouldRunSubgraphHealthcheck?: boolean;
|
|
103
|
+
onFailureToFetchSupergraphSdlDuringInit?: FailureToFetchSupergraphSdlDuringInit;
|
|
104
|
+
onFailureToFetchSupergraphSdlAfterInit?: FailureToFetchSupergraphSdlAfterInit;
|
|
105
|
+
}) {
|
|
106
|
+
this.apiKey = apiKey;
|
|
107
|
+
this.graphRef = graphRef;
|
|
108
|
+
this.logger = logger ?? getDefaultLogger(debug);
|
|
109
|
+
|
|
110
|
+
this.uplinkEndpoints = uplinkEndpoints ?? this.uplinkEndpoints;
|
|
111
|
+
// If the user didn't pass a `maxRetries`, default to trying each endpoint
|
|
112
|
+
// 3 times (minus 1 for the initial request) since we round-robin through
|
|
113
|
+
// each URL on failure
|
|
114
|
+
this.maxRetries = maxRetries ?? this.uplinkEndpoints.length * 3 - 1;
|
|
115
|
+
this.initialMaxRetries = initialMaxRetries ?? this.maxRetries;
|
|
116
|
+
|
|
117
|
+
this.pollIntervalMs = fallbackPollIntervalInMs ?? this.pollIntervalMs;
|
|
118
|
+
if (this.pollIntervalMs < UplinkSupergraphManager.MIN_POLL_INTERVAL_MS) {
|
|
119
|
+
this.logger.warn(
|
|
120
|
+
'Polling Apollo services at a frequency of less than once per 10 seconds (10000) is disallowed. Instead, the minimum allowed pollInterval of 10000 will be used. Please reconfigure your `fallbackPollIntervalInMs` accordingly. If this is problematic for your team, please contact support.',
|
|
121
|
+
);
|
|
122
|
+
this.pollIntervalMs = UplinkSupergraphManager.MIN_POLL_INTERVAL_MS;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.shouldRunSubgraphHealthcheck =
|
|
126
|
+
shouldRunSubgraphHealthcheck ?? this.shouldRunSubgraphHealthcheck;
|
|
127
|
+
this.onFailureToFetchSupergraphSdlDuringInit =
|
|
128
|
+
onFailureToFetchSupergraphSdlDuringInit;
|
|
129
|
+
this.onFailureToFetchSupergraphSdlAfterInit =
|
|
130
|
+
onFailureToFetchSupergraphSdlAfterInit;
|
|
131
|
+
|
|
132
|
+
this.state = { phase: 'constructed' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async initialize({ update, healthCheck }: SupergraphSdlHookOptions) {
|
|
136
|
+
this.update = update;
|
|
137
|
+
|
|
138
|
+
if (this.shouldRunSubgraphHealthcheck) {
|
|
139
|
+
this.healthCheck = healthCheck;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let initialSupergraphSdl: string | null = null;
|
|
143
|
+
try {
|
|
144
|
+
initialSupergraphSdl = await this.updateSupergraphSdl(
|
|
145
|
+
this.initialMaxRetries,
|
|
146
|
+
);
|
|
147
|
+
if (!initialSupergraphSdl) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
'Invalid supergraph schema supplied during initialization.',
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
this.logUpdateFailure(e);
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.state = { phase: 'initialized' };
|
|
158
|
+
|
|
159
|
+
// Start polling after we resolve the first supergraph
|
|
160
|
+
this.beginPolling();
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
supergraphSdl: initialSupergraphSdl,
|
|
164
|
+
cleanup: async () => {
|
|
165
|
+
if (this.state.phase === 'polling') {
|
|
166
|
+
await this.state.pollingPromise;
|
|
167
|
+
}
|
|
168
|
+
this.state = { phase: 'stopped' };
|
|
169
|
+
if (this.timerRef) {
|
|
170
|
+
clearTimeout(this.timerRef);
|
|
171
|
+
this.timerRef = null;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public async nextFetch(): Promise<void | null> {
|
|
178
|
+
if (this.state.phase !== 'polling') {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
return this.state.nextFetchPromise;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Configuration priority order:
|
|
186
|
+
* 1. APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT environment variable
|
|
187
|
+
* 2. default (GCP and AWS)
|
|
188
|
+
*/
|
|
189
|
+
public static getUplinkEndpoints(): string[] {
|
|
190
|
+
const envEndpoints =
|
|
191
|
+
process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT?.split(',');
|
|
192
|
+
return envEndpoints ?? UplinkSupergraphManager.DEFAULT_UPLINK_ENDPOINTS;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async updateSupergraphSdl(
|
|
196
|
+
maxRetries: number,
|
|
197
|
+
): Promise<string | null> {
|
|
198
|
+
let supergraphSdl;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
const result = await loadSupergraphSdlFromUplinks({
|
|
202
|
+
graphRef: this.graphRef,
|
|
203
|
+
apiKey: this.apiKey,
|
|
204
|
+
endpoints: this.uplinkEndpoints,
|
|
205
|
+
errorReportingEndpoint: this.errorReportingEndpoint,
|
|
206
|
+
fetcher: this.fetcher,
|
|
207
|
+
compositionId: this.compositionId ?? null,
|
|
208
|
+
maxRetries,
|
|
209
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
210
|
+
roundRobinSeed: this.fetchCount++,
|
|
211
|
+
logger: this.logger,
|
|
212
|
+
});
|
|
213
|
+
this.mostRecentSuccessfulFetchAt = new Date();
|
|
214
|
+
|
|
215
|
+
this.logger.debug(
|
|
216
|
+
`Received Uplink response. Has updated SDL? ${!!result?.supergraphSdl}`,
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (!result) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.compositionId = result.id;
|
|
224
|
+
|
|
225
|
+
supergraphSdl = result.supergraphSdl;
|
|
226
|
+
if (result?.minDelaySeconds) {
|
|
227
|
+
this.pollIntervalMs = result.minDelaySeconds * 1000;
|
|
228
|
+
}
|
|
229
|
+
} catch (e) {
|
|
230
|
+
this.logger.debug(
|
|
231
|
+
`Error fetching supergraphSdl from Uplink during phase '${this.state.phase}'`,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
this.state.phase === 'constructed' &&
|
|
236
|
+
this.onFailureToFetchSupergraphSdlDuringInit
|
|
237
|
+
) {
|
|
238
|
+
supergraphSdl = await this.onFailureToFetchSupergraphSdlDuringInit({
|
|
239
|
+
error: e,
|
|
240
|
+
graphRef: this.graphRef,
|
|
241
|
+
logger: this.logger,
|
|
242
|
+
fetchCount: this.fetchCount,
|
|
243
|
+
});
|
|
244
|
+
} else if (
|
|
245
|
+
this.state.phase === 'polling' &&
|
|
246
|
+
this.onFailureToFetchSupergraphSdlAfterInit
|
|
247
|
+
) {
|
|
248
|
+
supergraphSdl = await this.onFailureToFetchSupergraphSdlAfterInit({
|
|
249
|
+
error: e,
|
|
250
|
+
graphRef: this.graphRef,
|
|
251
|
+
logger: this.logger,
|
|
252
|
+
fetchCount: this.fetchCount,
|
|
253
|
+
mostRecentSuccessfulFetchAt: this.mostRecentSuccessfulFetchAt,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// This is really an error, but we'll let the caller decide what to do with it
|
|
257
|
+
if (!supergraphSdl) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
throw e;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// the healthCheck fn is only assigned if it's enabled in the config
|
|
266
|
+
await this.healthCheck?.(supergraphSdl);
|
|
267
|
+
return supergraphSdl;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private beginPolling() {
|
|
271
|
+
this.state = { phase: 'polling' };
|
|
272
|
+
this.poll();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private poll() {
|
|
276
|
+
if (this.state.phase !== 'polling') {
|
|
277
|
+
this.logger.debug(`Stopped polling Uplink [phase: ${this.state.phase}]`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.state.nextFetchPromise = resolvable();
|
|
282
|
+
|
|
283
|
+
this.logger.debug(
|
|
284
|
+
`Will poll Uplink after ${this.pollIntervalMs}ms [phase: ${this.state.phase}]`,
|
|
285
|
+
);
|
|
286
|
+
this.timerRef = setTimeout(async () => {
|
|
287
|
+
if (this.state.phase === 'polling') {
|
|
288
|
+
const pollingPromise = resolvable();
|
|
289
|
+
this.state.pollingPromise = pollingPromise;
|
|
290
|
+
try {
|
|
291
|
+
const supergraphSdl = await this.updateSupergraphSdl(this.maxRetries);
|
|
292
|
+
if (supergraphSdl) {
|
|
293
|
+
this.update?.(supergraphSdl);
|
|
294
|
+
}
|
|
295
|
+
} catch (e) {
|
|
296
|
+
this.logUpdateFailure(e);
|
|
297
|
+
}
|
|
298
|
+
pollingPromise.resolve();
|
|
299
|
+
this.state.nextFetchPromise?.resolve();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.poll();
|
|
303
|
+
}, this.pollIntervalMs);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private logUpdateFailure(e: any) {
|
|
307
|
+
this.logger.error(
|
|
308
|
+
'UplinkSupergraphManager failed to update supergraph with the following error: ' +
|
|
309
|
+
(e.message ?? e),
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { GraphQLError } from 'graphql';
|
|
2
2
|
import retry from 'async-retry';
|
|
3
|
+
import { AbortController } from "node-abort-controller";
|
|
3
4
|
import { SupergraphSdlUpdate } from '../../config';
|
|
4
5
|
import { submitOutOfBandReportIfConfigured } from './outOfBandReporter';
|
|
5
6
|
import { SupergraphSdlQuery } from '../../__generated__/graphqlTypes';
|
|
7
|
+
import type { FetcherResponse } from '@apollo/utils.fetcher';
|
|
8
|
+
import type { Logger } from '@apollo/utils.logger';
|
|
6
9
|
import type {
|
|
7
|
-
Fetcher,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from '@apollo/utils.fetcher';
|
|
10
|
+
AbortableFetcher as Fetcher,
|
|
11
|
+
AbortableFetcherRequestInit as FetcherRequestInit,
|
|
12
|
+
} from './types';
|
|
11
13
|
|
|
12
14
|
// Magic /* GraphQL */ comment below is for codegen, do not remove
|
|
13
15
|
export const SUPERGRAPH_SDL_QUERY = /* GraphQL */`#graphql
|
|
@@ -60,8 +62,9 @@ export async function loadSupergraphSdlFromUplinks({
|
|
|
60
62
|
fetcher,
|
|
61
63
|
compositionId,
|
|
62
64
|
maxRetries,
|
|
65
|
+
requestTimeoutMs,
|
|
63
66
|
roundRobinSeed,
|
|
64
|
-
|
|
67
|
+
logger,
|
|
65
68
|
}: {
|
|
66
69
|
graphRef: string;
|
|
67
70
|
apiKey: string;
|
|
@@ -70,8 +73,9 @@ export async function loadSupergraphSdlFromUplinks({
|
|
|
70
73
|
fetcher: Fetcher;
|
|
71
74
|
compositionId: string | null;
|
|
72
75
|
maxRetries: number,
|
|
76
|
+
requestTimeoutMs: number,
|
|
73
77
|
roundRobinSeed: number,
|
|
74
|
-
|
|
78
|
+
logger: Logger,
|
|
75
79
|
}) : Promise<SupergraphSdlUpdate | null> {
|
|
76
80
|
// This Promise resolves with either an updated supergraph or null if no change.
|
|
77
81
|
// This Promise can reject in the case that none of the retries are successful,
|
|
@@ -84,17 +88,18 @@ export async function loadSupergraphSdlFromUplinks({
|
|
|
84
88
|
endpoint: endpoints[roundRobinSeed++ % endpoints.length],
|
|
85
89
|
errorReportingEndpoint,
|
|
86
90
|
fetcher,
|
|
91
|
+
requestTimeoutMs,
|
|
87
92
|
compositionId,
|
|
93
|
+
logger,
|
|
88
94
|
}),
|
|
89
95
|
{
|
|
90
96
|
retries: maxRetries,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
97
|
+
maxTimeout: 60_000,
|
|
98
|
+
onRetry(e, attempt) {
|
|
99
|
+
logger.debug(`Unable to fetch supergraph SDL (attempt ${attempt}), waiting before retry: ${e}`);
|
|
100
|
+
},
|
|
95
101
|
},
|
|
96
102
|
);
|
|
97
|
-
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
export async function loadSupergraphSdlFromStorage({
|
|
@@ -103,14 +108,18 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
103
108
|
endpoint,
|
|
104
109
|
errorReportingEndpoint,
|
|
105
110
|
fetcher,
|
|
111
|
+
requestTimeoutMs,
|
|
106
112
|
compositionId,
|
|
113
|
+
logger,
|
|
107
114
|
}: {
|
|
108
115
|
graphRef: string;
|
|
109
116
|
apiKey: string;
|
|
110
117
|
endpoint: string;
|
|
111
118
|
errorReportingEndpoint?: string;
|
|
112
119
|
fetcher: Fetcher;
|
|
120
|
+
requestTimeoutMs: number;
|
|
113
121
|
compositionId: string | null;
|
|
122
|
+
logger: Logger;
|
|
114
123
|
}) : Promise<SupergraphSdlUpdate | null> {
|
|
115
124
|
const requestBody = JSON.stringify({
|
|
116
125
|
query: SUPERGRAPH_SDL_QUERY,
|
|
@@ -121,6 +130,12 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
121
130
|
},
|
|
122
131
|
})
|
|
123
132
|
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
const signal = setTimeout(() => {
|
|
135
|
+
logger.debug(`Aborting request due to timeout`);
|
|
136
|
+
controller.abort();
|
|
137
|
+
}, requestTimeoutMs);
|
|
138
|
+
|
|
124
139
|
const requestDetails: FetcherRequestInit = {
|
|
125
140
|
method: 'POST',
|
|
126
141
|
body: requestBody,
|
|
@@ -130,8 +145,11 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
130
145
|
'user-agent': `${name}/${version}`,
|
|
131
146
|
'content-type': 'application/json',
|
|
132
147
|
},
|
|
148
|
+
signal: controller.signal,
|
|
133
149
|
};
|
|
134
150
|
|
|
151
|
+
logger.debug(`🔧 Fetching ${graphRef} supergraph schema from ${endpoint} ifAfterId ${compositionId}`);
|
|
152
|
+
|
|
135
153
|
const startTime = new Date();
|
|
136
154
|
let result: FetcherResponse;
|
|
137
155
|
try {
|
|
@@ -150,6 +168,8 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
150
168
|
});
|
|
151
169
|
|
|
152
170
|
throw new UplinkFetcherError(fetchErrorMsg + (e.message ?? e));
|
|
171
|
+
} finally {
|
|
172
|
+
clearTimeout(signal);
|
|
153
173
|
}
|
|
154
174
|
|
|
155
175
|
const endTime = new Date();
|
|
@@ -192,7 +212,7 @@ export async function loadSupergraphSdlFromStorage({
|
|
|
192
212
|
minDelaySeconds,
|
|
193
213
|
// messages,
|
|
194
214
|
} = routerConfig;
|
|
195
|
-
return { id, supergraphSdl
|
|
215
|
+
return { id, supergraphSdl, minDelaySeconds };
|
|
196
216
|
} else if (routerConfig.__typename === 'FetchError') {
|
|
197
217
|
// FetchError case
|
|
198
218
|
const { code, message } = routerConfig;
|
package/src/supergraphManagers/{UplinkFetcher → UplinkSupergraphManager}/outOfBandReporter.ts
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FetcherResponse } from '@apollo/utils.fetcher';
|
|
2
2
|
import { GraphQLError } from 'graphql';
|
|
3
3
|
import {
|
|
4
4
|
ErrorCode,
|
|
5
5
|
OobReportMutation,
|
|
6
6
|
OobReportMutationVariables,
|
|
7
7
|
} from '../../__generated__/graphqlTypes';
|
|
8
|
+
import { type AbortableFetcher as Fetcher } from './types';
|
|
8
9
|
|
|
9
10
|
// Magic /* GraphQL */ comment below is for codegen, do not remove
|
|
10
11
|
export const OUT_OF_BAND_REPORTER_QUERY = /* GraphQL */`#graphql
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AbortSignal } from 'node-abort-controller';
|
|
2
|
+
import type { Fetcher, FetcherRequestInit } from '@apollo/utils.fetcher';
|
|
3
|
+
|
|
4
|
+
export interface AbortableFetcherRequestInit extends FetcherRequestInit {
|
|
5
|
+
signal?: AbortSignal | null | undefined;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export interface AbortableFetcher extends Fetcher {
|
|
9
|
+
init?: AbortableFetcherRequestInit;
|
|
10
|
+
};
|