@apollo/gateway 0.43.0 → 0.45.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -2
- 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 +3 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -17
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -33
- package/dist/index.js.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.d.ts +15 -6
- package/dist/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/loadSupergraphSdlFromStorage.js +40 -9
- package/dist/loadSupergraphSdlFromStorage.js.map +1 -1
- package/dist/outOfBandReporter.d.ts +10 -12
- package/dist/outOfBandReporter.d.ts.map +1 -1
- package/dist/outOfBandReporter.js +70 -73
- package/dist/outOfBandReporter.js.map +1 -1
- package/package.json +8 -8
- package/src/__generated__/graphqlTypes.ts +13 -11
- package/src/__tests__/gateway/reporting.test.ts +5 -3
- package/src/__tests__/integration/configuration.test.ts +32 -11
- package/src/__tests__/integration/networkRequests.test.ts +44 -32
- package/src/__tests__/integration/nockMocks.ts +42 -8
- package/src/__tests__/loadSupergraphSdlFromStorage.test.ts +129 -375
- package/src/__tests__/nockAssertions.ts +20 -0
- package/src/config.ts +10 -43
- package/src/index.ts +43 -54
- package/src/loadSupergraphSdlFromStorage.ts +61 -12
- package/src/outOfBandReporter.ts +87 -89
- 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/src/__tests__/integration/legacyNetworkRequests.test.ts +0 -279
- package/src/__tests__/integration/legacyNockMocks.ts +0 -113
- package/src/legacyLoadServicesFromStorage.ts +0 -170
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import nock from 'nock';
|
|
2
|
+
|
|
3
|
+
// Ensures an active and clean nock before every test
|
|
4
|
+
export function nockBeforeEach() {
|
|
5
|
+
if (!nock.isActive()) {
|
|
6
|
+
nock.activate();
|
|
7
|
+
}
|
|
8
|
+
// Cleaning _before_ each test ensures that any mocks from a previous test
|
|
9
|
+
// which failed don't affect the current test.
|
|
10
|
+
nock.cleanAll();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Ensures a test is complete (all expected requests were run) and a clean
|
|
14
|
+
// global state after each test.
|
|
15
|
+
export function nockAfterEach() {
|
|
16
|
+
// unmock HTTP interceptor
|
|
17
|
+
nock.restore();
|
|
18
|
+
// effectively nock.isDone() but with more helpful messages in test failures
|
|
19
|
+
expect(nock.activeMocks()).toEqual([]);
|
|
20
|
+
};
|
package/src/config.ts
CHANGED
|
@@ -135,31 +135,16 @@ export interface RemoteGatewayConfig extends GatewayConfigBase {
|
|
|
135
135
|
| ((service: ServiceEndpointDefinition) => Promise<HeadersInit> | HeadersInit);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
export interface LegacyManagedGatewayConfig extends GatewayConfigBase {
|
|
140
|
-
federationVersion?: number;
|
|
141
|
-
/**
|
|
142
|
-
* Setting this to null will cause the gateway to use the old mechanism for
|
|
143
|
-
* managed federation via GCS + composition.
|
|
144
|
-
*/
|
|
145
|
-
schemaConfigDeliveryEndpoint: null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// TODO(trevor:cloudconfig): This type becomes the only managed config
|
|
149
|
-
export interface PrecomposedManagedGatewayConfig extends GatewayConfigBase {
|
|
138
|
+
export interface ManagedGatewayConfig extends GatewayConfigBase {
|
|
150
139
|
/**
|
|
151
140
|
* This configuration option shouldn't be used unless by recommendation from
|
|
152
|
-
* Apollo staff.
|
|
153
|
-
* to the previous mechanism for managed federation.
|
|
141
|
+
* Apollo staff.
|
|
154
142
|
*/
|
|
155
|
-
schemaConfigDeliveryEndpoint?: string;
|
|
143
|
+
schemaConfigDeliveryEndpoint?: string; // deprecated
|
|
144
|
+
uplinkEndpoints?: string[];
|
|
145
|
+
uplinkMaxRetries?: number;
|
|
156
146
|
}
|
|
157
147
|
|
|
158
|
-
// TODO(trevor:cloudconfig): This union is no longer needed
|
|
159
|
-
export type ManagedGatewayConfig =
|
|
160
|
-
| LegacyManagedGatewayConfig
|
|
161
|
-
| PrecomposedManagedGatewayConfig;
|
|
162
|
-
|
|
163
148
|
interface ManuallyManagedServiceDefsGatewayConfig extends GatewayConfigBase {
|
|
164
149
|
experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions;
|
|
165
150
|
}
|
|
@@ -216,30 +201,12 @@ export function isManuallyManagedConfig(
|
|
|
216
201
|
export function isManagedConfig(
|
|
217
202
|
config: GatewayConfig,
|
|
218
203
|
): config is ManagedGatewayConfig {
|
|
219
|
-
return isPrecomposedManagedConfig(config) || isLegacyManagedConfig(config);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// TODO(trevor:cloudconfig): This merges with `isManagedConfig`
|
|
223
|
-
export function isPrecomposedManagedConfig(
|
|
224
|
-
config: GatewayConfig,
|
|
225
|
-
): config is PrecomposedManagedGatewayConfig {
|
|
226
|
-
return (
|
|
227
|
-
!isLegacyManagedConfig(config) &&
|
|
228
|
-
(('schemaConfigDeliveryEndpoint' in config &&
|
|
229
|
-
typeof config.schemaConfigDeliveryEndpoint === 'string') ||
|
|
230
|
-
(!isRemoteConfig(config) &&
|
|
231
|
-
!isLocalConfig(config) &&
|
|
232
|
-
!isSupergraphSdlConfig(config) &&
|
|
233
|
-
!isManuallyManagedConfig(config)))
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function isLegacyManagedConfig(
|
|
238
|
-
config: GatewayConfig,
|
|
239
|
-
): config is LegacyManagedGatewayConfig {
|
|
240
204
|
return (
|
|
241
|
-
'schemaConfigDeliveryEndpoint' in config
|
|
242
|
-
config
|
|
205
|
+
'schemaConfigDeliveryEndpoint' in config ||
|
|
206
|
+
(!isRemoteConfig(config) &&
|
|
207
|
+
!isLocalConfig(config) &&
|
|
208
|
+
!isSupergraphSdlConfig(config) &&
|
|
209
|
+
!isManuallyManagedConfig(config))
|
|
243
210
|
);
|
|
244
211
|
}
|
|
245
212
|
|
package/src/index.ts
CHANGED
|
@@ -66,12 +66,9 @@ import {
|
|
|
66
66
|
ServiceDefinitionUpdate,
|
|
67
67
|
SupergraphSdlUpdate,
|
|
68
68
|
CompositionUpdate,
|
|
69
|
-
isPrecomposedManagedConfig,
|
|
70
|
-
isLegacyManagedConfig,
|
|
71
69
|
} from './config';
|
|
72
|
-
import { loadSupergraphSdlFromStorage } from './loadSupergraphSdlFromStorage';
|
|
73
|
-
import { getServiceDefinitionsFromStorage } from './legacyLoadServicesFromStorage';
|
|
74
70
|
import { buildComposedSchema } from '@apollo/query-planner';
|
|
71
|
+
import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
|
|
75
72
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
76
73
|
import { OpenTelemetrySpanNames, tracer } from './utilities/opentelemetry';
|
|
77
74
|
import { CoreSchema } from '@apollo/core-schema';
|
|
@@ -114,20 +111,6 @@ export function getDefaultFetcher() {
|
|
|
114
111
|
});
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
/**
|
|
118
|
-
* TODO(trevor:cloudconfig): Stop exporting this
|
|
119
|
-
* @deprecated This will be removed in a future version of @apollo/gateway
|
|
120
|
-
*/
|
|
121
|
-
export const getDefaultGcsFetcher = deprecate(
|
|
122
|
-
getDefaultFetcher,
|
|
123
|
-
`'getDefaultGcsFetcher' is deprecated. Use 'getDefaultFetcher' instead.`,
|
|
124
|
-
);
|
|
125
|
-
/**
|
|
126
|
-
* TODO(trevor:cloudconfig): Stop exporting this
|
|
127
|
-
* @deprecated This will be removed in a future version of @apollo/gateway
|
|
128
|
-
*/
|
|
129
|
-
export const GCS_RETRY_COUNT = 5;
|
|
130
|
-
|
|
131
114
|
export const HEALTH_CHECK_QUERY =
|
|
132
115
|
'query __ApolloServiceHealthCheck__ { __typename }';
|
|
133
116
|
export const SERVICE_DEFINITION_QUERY =
|
|
@@ -193,10 +176,13 @@ export class ApolloGateway implements GraphQLService {
|
|
|
193
176
|
private serviceSdlCache = new Map<string, string>();
|
|
194
177
|
private warnedStates: WarnedStates = Object.create(null);
|
|
195
178
|
private queryPlanner?: QueryPlanner;
|
|
179
|
+
private supergraphSdl?: string;
|
|
196
180
|
private parsedSupergraphSdl?: DocumentNode;
|
|
197
181
|
private fetcher: typeof fetch;
|
|
198
182
|
private compositionId?: string;
|
|
199
183
|
private state: GatewayState;
|
|
184
|
+
private errorReportingEndpoint: string | undefined =
|
|
185
|
+
process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
|
|
200
186
|
|
|
201
187
|
// Observe query plan, service info, and operation info prior to execution.
|
|
202
188
|
// The information made available here will give insight into the resulting
|
|
@@ -214,12 +200,11 @@ export class ApolloGateway implements GraphQLService {
|
|
|
214
200
|
private updateServiceDefinitions: Experimental_UpdateComposition;
|
|
215
201
|
// how often service defs should be loaded/updated (in ms)
|
|
216
202
|
private experimental_pollInterval?: number;
|
|
217
|
-
// Configure the
|
|
218
|
-
// *
|
|
219
|
-
// * `null` will revert the gateway to legacy mode (polling GCS and composing the schema itself).
|
|
203
|
+
// Configure the endpoints by which gateway will access its precomposed schema.
|
|
204
|
+
// * An array of URLs means use these endpoints to obtain schema, if one is unavailable then try the next.
|
|
220
205
|
// * `undefined` means the gateway is not using managed federation
|
|
221
|
-
|
|
222
|
-
private
|
|
206
|
+
private uplinkEndpoints?: string[];
|
|
207
|
+
private uplinkMaxRetries?: number;
|
|
223
208
|
|
|
224
209
|
constructor(config?: GatewayConfig) {
|
|
225
210
|
this.config = {
|
|
@@ -247,19 +232,23 @@ export class ApolloGateway implements GraphQLService {
|
|
|
247
232
|
this.experimental_pollInterval = config?.experimental_pollInterval;
|
|
248
233
|
|
|
249
234
|
// 1. If config is set to a `string`, use it
|
|
250
|
-
// 2. If
|
|
251
|
-
// 3. If
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
235
|
+
// 2. If the env var is set, use that
|
|
236
|
+
// 3. If config is `undefined`, use the default uplink URLs
|
|
237
|
+
if (isManagedConfig(this.config)) {
|
|
238
|
+
const rawEndpointsString = process.env.APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT;
|
|
239
|
+
const envEndpoints = rawEndpointsString?.split(",") ?? null;
|
|
240
|
+
|
|
241
|
+
if (this.config.schemaConfigDeliveryEndpoint && !this.config.uplinkEndpoints) {
|
|
242
|
+
this.uplinkEndpoints = [this.config.schemaConfigDeliveryEndpoint];
|
|
243
|
+
} else {
|
|
244
|
+
this.uplinkEndpoints = this.config.uplinkEndpoints ??
|
|
245
|
+
envEndpoints ?? [
|
|
246
|
+
'https://uplink.api.apollographql.com/',
|
|
247
|
+
'https://aws.uplink.api.apollographql.com/'
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.uplinkMaxRetries = this.config.uplinkMaxRetries ?? this.uplinkEndpoints.length * 3;
|
|
263
252
|
}
|
|
264
253
|
|
|
265
254
|
if (isManuallyManagedConfig(this.config)) {
|
|
@@ -447,6 +436,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
447
436
|
: this.createSchemaFromSupergraphSdl(config.supergraphSdl));
|
|
448
437
|
// TODO(trevor): #580 redundant parse
|
|
449
438
|
this.parsedSupergraphSdl = parse(supergraphSdl);
|
|
439
|
+
this.supergraphSdl = supergraphSdl;
|
|
450
440
|
this.updateWithSchemaAndNotify(schema, supergraphSdl, true);
|
|
451
441
|
} catch (e) {
|
|
452
442
|
this.state = { phase: 'failed to load' };
|
|
@@ -576,6 +566,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
576
566
|
await this.maybePerformServiceHealthCheck(result);
|
|
577
567
|
|
|
578
568
|
this.compositionId = result.id;
|
|
569
|
+
this.supergraphSdl = result.supergraphSdl;
|
|
579
570
|
this.parsedSupergraphSdl = parsedSupergraphSdl;
|
|
580
571
|
|
|
581
572
|
const { schema, supergraphSdl } = this.createSchemaFromSupergraphSdl(
|
|
@@ -977,24 +968,22 @@ export class ApolloGateway implements GraphQLService {
|
|
|
977
968
|
);
|
|
978
969
|
}
|
|
979
970
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
throw new Error('Programming error: unhandled configuration');
|
|
997
|
-
}
|
|
971
|
+
const result = await loadSupergraphSdlFromUplinks({
|
|
972
|
+
graphRef: this.apolloConfig!.graphRef!,
|
|
973
|
+
apiKey: this.apolloConfig!.key!,
|
|
974
|
+
endpoints: this.uplinkEndpoints!,
|
|
975
|
+
errorReportingEndpoint: this.errorReportingEndpoint,
|
|
976
|
+
fetcher: this.fetcher,
|
|
977
|
+
compositionId: this.compositionId ?? null,
|
|
978
|
+
maxRetries: this.uplinkMaxRetries!,
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
return (
|
|
982
|
+
result ?? {
|
|
983
|
+
id: this.compositionId!,
|
|
984
|
+
supergraphSdl: this.supergraphSdl!,
|
|
985
|
+
}
|
|
986
|
+
);
|
|
998
987
|
}
|
|
999
988
|
|
|
1000
989
|
private maybeWarnOnConflictingConfig() {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { fetch, Response, Request } from 'apollo-server-env';
|
|
2
2
|
import { GraphQLError } from 'graphql';
|
|
3
|
-
import {
|
|
3
|
+
import { SupergraphSdlUpdate } from './config';
|
|
4
|
+
import { submitOutOfBandReportIfConfigured } from './outOfBandReporter';
|
|
4
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
|
|
@@ -38,17 +39,63 @@ 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
|
}
|
package/src/outOfBandReporter.ts
CHANGED
|
@@ -27,102 +27,100 @@ interface OobReportMutationFailure {
|
|
|
27
27
|
data?: OobReportMutation;
|
|
28
28
|
errors: GraphQLError[];
|
|
29
29
|
}
|
|
30
|
-
export class OutOfBandReporter {
|
|
31
|
-
static endpoint: string | null = process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT || null;
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
export async function submitOutOfBandReportIfConfigured({
|
|
32
|
+
error,
|
|
33
|
+
request,
|
|
34
|
+
endpoint,
|
|
35
|
+
response,
|
|
36
|
+
startedAt,
|
|
37
|
+
endedAt,
|
|
38
|
+
tags,
|
|
39
|
+
fetcher,
|
|
40
|
+
}: {
|
|
41
|
+
error: Error;
|
|
42
|
+
request: Request;
|
|
43
|
+
endpoint: string | undefined;
|
|
44
|
+
response?: Response;
|
|
45
|
+
startedAt: Date;
|
|
46
|
+
endedAt: Date;
|
|
47
|
+
tags?: string[];
|
|
48
|
+
fetcher: typeof fetch;
|
|
49
|
+
}) {
|
|
50
|
+
// don't send report if the endpoint url is not configured
|
|
51
|
+
if (!endpoint) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
55
|
+
let errorCode: ErrorCode;
|
|
56
|
+
if (!response) {
|
|
57
|
+
errorCode = ErrorCode.ConnectionFailed;
|
|
58
|
+
} else {
|
|
59
|
+
// possible error situations to check against
|
|
60
|
+
switch (response.status) {
|
|
61
|
+
case 400:
|
|
62
|
+
case 413:
|
|
63
|
+
case 422:
|
|
64
|
+
errorCode = ErrorCode.InvalidBody;
|
|
65
|
+
break;
|
|
66
|
+
case 408:
|
|
67
|
+
case 504:
|
|
68
|
+
errorCode = ErrorCode.Timeout;
|
|
69
|
+
break;
|
|
70
|
+
case 502:
|
|
71
|
+
case 503:
|
|
72
|
+
errorCode = ErrorCode.ConnectionFailed;
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
errorCode = ErrorCode.Other;
|
|
78
76
|
}
|
|
77
|
+
}
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
const responseBody: string | undefined = await response?.text();
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
},
|
|
92
|
-
response: response
|
|
93
|
-
? {
|
|
94
|
-
httpStatusCode: response.status,
|
|
95
|
-
body: responseBody,
|
|
96
|
-
}
|
|
97
|
-
: null,
|
|
98
|
-
startedAt: startedAt.toISOString(),
|
|
99
|
-
endedAt: endedAt.toISOString(),
|
|
100
|
-
tags: tags,
|
|
81
|
+
const variables: OobReportMutationVariables = {
|
|
82
|
+
input: {
|
|
83
|
+
error: {
|
|
84
|
+
code: errorCode,
|
|
85
|
+
message: error.message,
|
|
86
|
+
},
|
|
87
|
+
request: {
|
|
88
|
+
url: request.url,
|
|
89
|
+
body: await request.text(),
|
|
101
90
|
},
|
|
102
|
-
|
|
91
|
+
response: response
|
|
92
|
+
? {
|
|
93
|
+
httpStatusCode: response.status,
|
|
94
|
+
body: responseBody,
|
|
95
|
+
}
|
|
96
|
+
: null,
|
|
97
|
+
startedAt: startedAt.toISOString(),
|
|
98
|
+
endedAt: endedAt.toISOString(),
|
|
99
|
+
tags: tags,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
103
102
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
} catch (e) {
|
|
125
|
-
throw new Error(`Out-of-band error reporting failed: ${e.message ?? e}`);
|
|
103
|
+
try {
|
|
104
|
+
const oobResponse = await fetcher(endpoint, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
body: JSON.stringify({
|
|
107
|
+
query: OUT_OF_BAND_REPORTER_QUERY,
|
|
108
|
+
variables,
|
|
109
|
+
}),
|
|
110
|
+
headers: {
|
|
111
|
+
'apollographql-client-name': name,
|
|
112
|
+
'apollographql-client-version': version,
|
|
113
|
+
'user-agent': `${name}/${version}`,
|
|
114
|
+
'content-type': 'application/json',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const parsedResponse: OobReportMutationResult = await oobResponse.json();
|
|
118
|
+
if (!parsedResponse?.data?.reportError) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Out-of-band error reporting failed: ${oobResponse.status} ${oobResponse.statusText}`,
|
|
121
|
+
);
|
|
126
122
|
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
throw new Error(`Out-of-band error reporting failed: ${e.message ?? e}`);
|
|
127
125
|
}
|
|
128
126
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { fetch } from 'apollo-server-env';
|
|
2
|
-
import { ServiceDefinitionUpdate } from './config';
|
|
3
|
-
interface ImplementingServiceLocation {
|
|
4
|
-
name: string;
|
|
5
|
-
path: string;
|
|
6
|
-
}
|
|
7
|
-
export interface CompositionMetadata {
|
|
8
|
-
formatVersion: number;
|
|
9
|
-
id: string;
|
|
10
|
-
implementingServiceLocations: ImplementingServiceLocation[];
|
|
11
|
-
schemaHash: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function getServiceDefinitionsFromStorage({ graphRef, apiKeyHash, federationVersion, fetcher, }: {
|
|
14
|
-
graphRef: string;
|
|
15
|
-
apiKeyHash: string;
|
|
16
|
-
federationVersion: number;
|
|
17
|
-
fetcher: typeof fetch;
|
|
18
|
-
}): Promise<ServiceDefinitionUpdate>;
|
|
19
|
-
export {};
|
|
20
|
-
//# sourceMappingURL=legacyLoadServicesFromStorage.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"legacyLoadServicesFromStorage.d.ts","sourceRoot":"","sources":["../src/legacyLoadServicesFromStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAiBnD,UAAU,2BAA2B;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,4BAA4B,EAAE,2BAA2B,EAAE,CAAC;IAC5D,UAAU,EAAE,MAAM,CAAC;CACpB;AAsED,wBAAsB,gCAAgC,CAAC,EACrD,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,KAAK,CAAC;CACvB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA4DnC"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getServiceDefinitionsFromStorage = void 0;
|
|
4
|
-
const graphql_1 = require("graphql");
|
|
5
|
-
const envOverridePartialSchemaBaseUrl = 'APOLLO_PARTIAL_SCHEMA_BASE_URL';
|
|
6
|
-
const envOverrideStorageSecretBaseUrl = 'APOLLO_STORAGE_SECRET_BASE_URL';
|
|
7
|
-
const urlFromEnvOrDefault = (envKey, fallback) => (process.env[envKey] || fallback).replace(/\/$/, '');
|
|
8
|
-
const urlPartialSchemaBase = urlFromEnvOrDefault(envOverridePartialSchemaBaseUrl, 'https://federation.api.apollographql.com/');
|
|
9
|
-
const urlStorageSecretBase = urlFromEnvOrDefault(envOverrideStorageSecretBaseUrl, 'https://storage-secrets.api.apollographql.com/');
|
|
10
|
-
function getStorageSecretUrl(graphId, apiKeyHash) {
|
|
11
|
-
return `${urlStorageSecretBase}/${graphId}/storage-secret/${apiKeyHash}.json`;
|
|
12
|
-
}
|
|
13
|
-
function fetchApolloGcs(fetcher, ...args) {
|
|
14
|
-
const [input, init] = args;
|
|
15
|
-
const url = (typeof input === 'object' && input.url) || input;
|
|
16
|
-
return fetcher(input, init)
|
|
17
|
-
.catch((fetchError) => {
|
|
18
|
-
throw new Error('Cannot access Apollo storage: ' + fetchError);
|
|
19
|
-
})
|
|
20
|
-
.then(async (response) => {
|
|
21
|
-
if (response.ok || response.status === 304) {
|
|
22
|
-
return response;
|
|
23
|
-
}
|
|
24
|
-
const body = await response.text();
|
|
25
|
-
if (response.status === 403 && body.includes('AccessDenied')) {
|
|
26
|
-
throw new Error('Unable to authenticate with Apollo storage ' +
|
|
27
|
-
'while fetching ' +
|
|
28
|
-
url +
|
|
29
|
-
'. Ensure that the API key is ' +
|
|
30
|
-
'configured properly and that a federated service has been ' +
|
|
31
|
-
'pushed. For details, see ' +
|
|
32
|
-
'https://go.apollo.dev/g/resolve-access-denied.');
|
|
33
|
-
}
|
|
34
|
-
throw new Error('Could not communicate with Apollo storage: ' + body);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
async function getServiceDefinitionsFromStorage({ graphRef, apiKeyHash, federationVersion, fetcher, }) {
|
|
38
|
-
const at = graphRef.indexOf('@');
|
|
39
|
-
const graphId = at === -1 ? graphRef : graphRef.substring(0, at);
|
|
40
|
-
const graphVariant = at === -1 ? 'current' : graphRef.substring(at + 1);
|
|
41
|
-
const storageSecretUrl = getStorageSecretUrl(graphId, apiKeyHash);
|
|
42
|
-
const secret = await fetchApolloGcs(fetcher, storageSecretUrl).then((res) => res.json());
|
|
43
|
-
const baseUrl = `${urlPartialSchemaBase}/${secret}/${graphVariant}/v${federationVersion}`;
|
|
44
|
-
const compositionConfigResponse = await fetchApolloGcs(fetcher, `${baseUrl}/composition-config-link`);
|
|
45
|
-
if (compositionConfigResponse.status === 304) {
|
|
46
|
-
return { isNewSchema: false };
|
|
47
|
-
}
|
|
48
|
-
const linkFileResult = await compositionConfigResponse.json();
|
|
49
|
-
const compositionMetadata = await fetchApolloGcs(fetcher, `${urlPartialSchemaBase}/${linkFileResult.configPath}`).then((res) => res.json());
|
|
50
|
-
const serviceDefinitions = await Promise.all(compositionMetadata.implementingServiceLocations.map(async ({ name, path }) => {
|
|
51
|
-
const { url, partialSchemaPath } = await fetcher(`${urlPartialSchemaBase}/${path}`).then((response) => response.json());
|
|
52
|
-
const sdl = await fetcher(`${urlPartialSchemaBase}/${partialSchemaPath}`).then((response) => response.text());
|
|
53
|
-
return { name, url, typeDefs: (0, graphql_1.parse)(sdl) };
|
|
54
|
-
}));
|
|
55
|
-
return {
|
|
56
|
-
serviceDefinitions,
|
|
57
|
-
compositionMetadata,
|
|
58
|
-
isNewSchema: true,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
exports.getServiceDefinitionsFromStorage = getServiceDefinitionsFromStorage;
|
|
62
|
-
//# sourceMappingURL=legacyLoadServicesFromStorage.js.map
|