@apollo/gateway 0.48.1 → 0.50.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/README.md +7 -5
- package/dist/__generated__/graphqlTypes.d.ts +4 -0
- package/dist/__generated__/graphqlTypes.d.ts.map +1 -1
- package/dist/__generated__/graphqlTypes.js.map +1 -1
- package/dist/config.d.ts +6 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/executeQueryPlan.d.ts.map +1 -1
- package/dist/executeQueryPlan.js +4 -3
- package/dist/executeQueryPlan.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -8
- package/dist/index.js.map +1 -1
- package/dist/schema-helper/index.js +5 -1
- package/dist/schema-helper/index.js.map +1 -1
- package/dist/supergraphManagers/IntrospectAndCompose/index.js.map +1 -1
- package/dist/supergraphManagers/LegacyFetcher/index.js.map +1 -1
- package/dist/supergraphManagers/LocalCompose/index.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts +3 -1
- package/dist/supergraphManagers/UplinkFetcher/index.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/index.js +20 -4
- package/dist/supergraphManagers/UplinkFetcher/index.js.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts +3 -2
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.d.ts.map +1 -1
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js +9 -3
- package/dist/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.js.map +1 -1
- package/package.json +6 -6
- package/src/__generated__/graphqlTypes.ts +11 -2
- package/src/__tests__/CucumberREADME.md +1 -0
- package/src/__tests__/build-query-plan-fragmentization.feature +10 -0
- package/src/__tests__/build-query-plan.feature +84 -16
- package/src/__tests__/buildQueryPlan.test.ts +272 -1
- package/src/__tests__/gateway/lifecycle-hooks.test.ts +3 -3
- package/src/__tests__/gateway/reporting.test.ts +5 -0
- package/src/__tests__/gateway/supergraphSdl.test.ts +3 -3
- package/src/__tests__/integration/abstract-types.test.ts +3 -3
- package/src/__tests__/integration/configuration.test.ts +0 -11
- package/src/__tests__/integration/requires.test.ts +1 -1
- package/src/__tests__/integration/value-types.test.ts +1 -1
- package/src/config.ts +11 -6
- package/src/executeQueryPlan.ts +4 -0
- package/src/index.ts +12 -6
- package/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts +41 -0
- package/src/supergraphManagers/UplinkFetcher/index.ts +37 -18
- package/src/supergraphManagers/UplinkFetcher/loadSupergraphSdlFromStorage.ts +9 -1
|
@@ -7,7 +7,12 @@ import {
|
|
|
7
7
|
fixturesWithUpdate,
|
|
8
8
|
} from 'apollo-federation-integration-testsuite';
|
|
9
9
|
import { getFederatedTestingSchema } from './execution-utils';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
QueryPlanner,
|
|
12
|
+
FetchNode,
|
|
13
|
+
SequenceNode,
|
|
14
|
+
FlattenNode,
|
|
15
|
+
} from '@apollo/query-planner';
|
|
11
16
|
|
|
12
17
|
expect.addSnapshotSerializer(astSerializer);
|
|
13
18
|
expect.addSnapshotSerializer(queryPlanSerializer);
|
|
@@ -1935,4 +1940,270 @@ describe('buildQueryPlan', () => {
|
|
|
1935
1940
|
);
|
|
1936
1941
|
});
|
|
1937
1942
|
});
|
|
1943
|
+
|
|
1944
|
+
it(`when key is marked external inside another type`, () => {
|
|
1945
|
+
const operationString = `#graphql
|
|
1946
|
+
query {
|
|
1947
|
+
libraryAccount {
|
|
1948
|
+
description
|
|
1949
|
+
library {
|
|
1950
|
+
id
|
|
1951
|
+
name
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
`;
|
|
1956
|
+
|
|
1957
|
+
const operationDocument = gql(operationString);
|
|
1958
|
+
|
|
1959
|
+
const queryPlan = queryPlanner.buildQueryPlan(
|
|
1960
|
+
buildOperationContext({
|
|
1961
|
+
schema,
|
|
1962
|
+
operationDocument,
|
|
1963
|
+
}),
|
|
1964
|
+
);
|
|
1965
|
+
|
|
1966
|
+
expect(queryPlan).toMatchInlineSnapshot(`
|
|
1967
|
+
QueryPlan {
|
|
1968
|
+
Fetch(service: "accounts") {
|
|
1969
|
+
{
|
|
1970
|
+
libraryAccount {
|
|
1971
|
+
description
|
|
1972
|
+
library {
|
|
1973
|
+
id
|
|
1974
|
+
name
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
}
|
|
1980
|
+
`);
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
describe('fetch operation names', () => {
|
|
1984
|
+
it('handle subgraph with - in the name', () => {
|
|
1985
|
+
const subgraph1 = {
|
|
1986
|
+
name: 'S1',
|
|
1987
|
+
typeDefs: gql`
|
|
1988
|
+
type Query {
|
|
1989
|
+
t: T
|
|
1990
|
+
}
|
|
1991
|
+
type T @key(fields: "id") {
|
|
1992
|
+
id: ID!
|
|
1993
|
+
}
|
|
1994
|
+
`,
|
|
1995
|
+
};
|
|
1996
|
+
|
|
1997
|
+
const subgraph2 = {
|
|
1998
|
+
name: 'non-graphql-name',
|
|
1999
|
+
typeDefs: gql`
|
|
2000
|
+
extend type T @key(fields: "id") {
|
|
2001
|
+
id: ID! @external
|
|
2002
|
+
x: Int
|
|
2003
|
+
}
|
|
2004
|
+
`,
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
({ schema, queryPlanner } = getFederatedTestingSchema([
|
|
2008
|
+
subgraph1,
|
|
2009
|
+
subgraph2,
|
|
2010
|
+
]));
|
|
2011
|
+
|
|
2012
|
+
const operationDocument = gql`
|
|
2013
|
+
query myOp {
|
|
2014
|
+
t {
|
|
2015
|
+
x
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
`;
|
|
2019
|
+
|
|
2020
|
+
const plan = queryPlanner.buildQueryPlan(
|
|
2021
|
+
buildOperationContext({ schema, operationDocument }),
|
|
2022
|
+
);
|
|
2023
|
+
|
|
2024
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
2025
|
+
QueryPlan {
|
|
2026
|
+
Sequence {
|
|
2027
|
+
Fetch(service: "S1") {
|
|
2028
|
+
{
|
|
2029
|
+
t {
|
|
2030
|
+
__typename
|
|
2031
|
+
id
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
},
|
|
2035
|
+
Flatten(path: "t") {
|
|
2036
|
+
Fetch(service: "non-graphql-name") {
|
|
2037
|
+
{
|
|
2038
|
+
... on T {
|
|
2039
|
+
__typename
|
|
2040
|
+
id
|
|
2041
|
+
}
|
|
2042
|
+
} =>
|
|
2043
|
+
{
|
|
2044
|
+
... on T {
|
|
2045
|
+
x
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
},
|
|
2049
|
+
},
|
|
2050
|
+
},
|
|
2051
|
+
}
|
|
2052
|
+
`);
|
|
2053
|
+
const fetch = ((plan.node as SequenceNode).nodes[1] as FlattenNode)
|
|
2054
|
+
.node as FetchNode;
|
|
2055
|
+
expect(fetch.operation).toMatch(/^query myOp__non_graphql_name__1.*/i);
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
it('ensures sanitization applies repeatedly', () => {
|
|
2059
|
+
const subgraph1 = {
|
|
2060
|
+
name: 'S1',
|
|
2061
|
+
typeDefs: gql`
|
|
2062
|
+
type Query {
|
|
2063
|
+
t: T
|
|
2064
|
+
}
|
|
2065
|
+
type T @key(fields: "id") {
|
|
2066
|
+
id: ID!
|
|
2067
|
+
}
|
|
2068
|
+
`,
|
|
2069
|
+
};
|
|
2070
|
+
|
|
2071
|
+
const subgraph2 = {
|
|
2072
|
+
name: 'a-na&me-with-plen&ty-replace*ments',
|
|
2073
|
+
typeDefs: gql`
|
|
2074
|
+
extend type T @key(fields: "id") {
|
|
2075
|
+
id: ID! @external
|
|
2076
|
+
x: Int
|
|
2077
|
+
}
|
|
2078
|
+
`,
|
|
2079
|
+
};
|
|
2080
|
+
|
|
2081
|
+
({ schema, queryPlanner } = getFederatedTestingSchema([
|
|
2082
|
+
subgraph1,
|
|
2083
|
+
subgraph2,
|
|
2084
|
+
]));
|
|
2085
|
+
|
|
2086
|
+
const operationDocument = gql`
|
|
2087
|
+
query myOp {
|
|
2088
|
+
t {
|
|
2089
|
+
x
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
`;
|
|
2093
|
+
|
|
2094
|
+
const plan = queryPlanner.buildQueryPlan(
|
|
2095
|
+
buildOperationContext({ schema, operationDocument }),
|
|
2096
|
+
);
|
|
2097
|
+
|
|
2098
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
2099
|
+
QueryPlan {
|
|
2100
|
+
Sequence {
|
|
2101
|
+
Fetch(service: "S1") {
|
|
2102
|
+
{
|
|
2103
|
+
t {
|
|
2104
|
+
__typename
|
|
2105
|
+
id
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
2109
|
+
Flatten(path: "t") {
|
|
2110
|
+
Fetch(service: "a-na&me-with-plen&ty-replace*ments") {
|
|
2111
|
+
{
|
|
2112
|
+
... on T {
|
|
2113
|
+
__typename
|
|
2114
|
+
id
|
|
2115
|
+
}
|
|
2116
|
+
} =>
|
|
2117
|
+
{
|
|
2118
|
+
... on T {
|
|
2119
|
+
x
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
},
|
|
2123
|
+
},
|
|
2124
|
+
},
|
|
2125
|
+
}
|
|
2126
|
+
`);
|
|
2127
|
+
|
|
2128
|
+
const fetch = ((plan.node as SequenceNode).nodes[1] as FlattenNode)
|
|
2129
|
+
.node as FetchNode;
|
|
2130
|
+
expect(fetch.operation).toMatch(
|
|
2131
|
+
/^query myOp__a_name_with_plenty_replacements__1.*/i,
|
|
2132
|
+
);
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
it('handle very non-graph subgraph name', () => {
|
|
2136
|
+
const subgraph1 = {
|
|
2137
|
+
name: 'S1',
|
|
2138
|
+
typeDefs: gql`
|
|
2139
|
+
type Query {
|
|
2140
|
+
t: T
|
|
2141
|
+
}
|
|
2142
|
+
type T @key(fields: "id") {
|
|
2143
|
+
id: ID!
|
|
2144
|
+
}
|
|
2145
|
+
`,
|
|
2146
|
+
};
|
|
2147
|
+
|
|
2148
|
+
const subgraph2 = {
|
|
2149
|
+
name: '42!',
|
|
2150
|
+
typeDefs: gql`
|
|
2151
|
+
extend type T @key(fields: "id") {
|
|
2152
|
+
id: ID! @external
|
|
2153
|
+
x: Int
|
|
2154
|
+
}
|
|
2155
|
+
`,
|
|
2156
|
+
};
|
|
2157
|
+
|
|
2158
|
+
({ schema, queryPlanner } = getFederatedTestingSchema([
|
|
2159
|
+
subgraph1,
|
|
2160
|
+
subgraph2,
|
|
2161
|
+
]));
|
|
2162
|
+
|
|
2163
|
+
const operationDocument = gql`
|
|
2164
|
+
query myOp {
|
|
2165
|
+
t {
|
|
2166
|
+
x
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
`;
|
|
2170
|
+
|
|
2171
|
+
const plan = queryPlanner.buildQueryPlan(
|
|
2172
|
+
buildOperationContext({ schema, operationDocument }),
|
|
2173
|
+
);
|
|
2174
|
+
expect(plan).toMatchInlineSnapshot(`
|
|
2175
|
+
QueryPlan {
|
|
2176
|
+
Sequence {
|
|
2177
|
+
Fetch(service: "S1") {
|
|
2178
|
+
{
|
|
2179
|
+
t {
|
|
2180
|
+
__typename
|
|
2181
|
+
id
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
Flatten(path: "t") {
|
|
2186
|
+
Fetch(service: "42!") {
|
|
2187
|
+
{
|
|
2188
|
+
... on T {
|
|
2189
|
+
__typename
|
|
2190
|
+
id
|
|
2191
|
+
}
|
|
2192
|
+
} =>
|
|
2193
|
+
{
|
|
2194
|
+
... on T {
|
|
2195
|
+
x
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
},
|
|
2199
|
+
},
|
|
2200
|
+
},
|
|
2201
|
+
}
|
|
2202
|
+
`);
|
|
2203
|
+
const fetch = ((plan.node as SequenceNode).nodes[1] as FlattenNode)
|
|
2204
|
+
.node as FetchNode;
|
|
2205
|
+
|
|
2206
|
+
expect(fetch.operation).toMatch(/^query myOp___42__1.*/i);
|
|
2207
|
+
});
|
|
2208
|
+
});
|
|
1938
2209
|
});
|
|
@@ -135,13 +135,13 @@ describe('lifecycle hooks', () => {
|
|
|
135
135
|
|
|
136
136
|
const [firstCall, secondCall] = mockDidUpdate.mock.calls;
|
|
137
137
|
|
|
138
|
-
const expectedFirstId = '
|
|
138
|
+
const expectedFirstId = '1cb734eadae95dd1778f3fe3c9df6cbaecb95e80c2bcefed397c63ec72469032'
|
|
139
139
|
expect(firstCall[0]!.compositionId).toEqual(expectedFirstId);
|
|
140
140
|
// first call should have no second "previous" argument
|
|
141
141
|
expect(firstCall[1]).toBeUndefined();
|
|
142
142
|
|
|
143
143
|
expect(secondCall[0]!.compositionId).toEqual(
|
|
144
|
-
'
|
|
144
|
+
'5d24c0f42d0d372c2ad671567cee186e3680400dffbcc3430cb57b9dc04b2be2',
|
|
145
145
|
);
|
|
146
146
|
// second call should have previous info in the second arg
|
|
147
147
|
expect(secondCall[1]!.compositionId).toEqual(expectedFirstId);
|
|
@@ -177,7 +177,7 @@ describe('lifecycle hooks', () => {
|
|
|
177
177
|
);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
-
it('registers schema change callbacks when
|
|
180
|
+
it('registers schema change callbacks when pollIntervalInMs is set for unmanaged configs', async () => {
|
|
181
181
|
const experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions =
|
|
182
182
|
jest.fn(async (_config) => {
|
|
183
183
|
return { serviceDefinitions, isNewSchema: true };
|
|
@@ -228,6 +228,7 @@ describe('reporting', () => {
|
|
|
228
228
|
"seconds": "1562203363",
|
|
229
229
|
},
|
|
230
230
|
"header": "<HEADER>",
|
|
231
|
+
"operationCount": 1,
|
|
231
232
|
"tracesPerQuery": Object {
|
|
232
233
|
"# -
|
|
233
234
|
{me{name{first last}}topProducts{name}}": Object {
|
|
@@ -299,6 +300,7 @@ describe('reporting', () => {
|
|
|
299
300
|
"nanos": 123000000,
|
|
300
301
|
"seconds": "1562203363",
|
|
301
302
|
},
|
|
303
|
+
"fieldExecutionWeight": 1,
|
|
302
304
|
"root": Object {
|
|
303
305
|
"child": Array [
|
|
304
306
|
Object {
|
|
@@ -364,6 +366,7 @@ describe('reporting', () => {
|
|
|
364
366
|
"nanos": 123000000,
|
|
365
367
|
"seconds": "1562203363",
|
|
366
368
|
},
|
|
369
|
+
"fieldExecutionWeight": 1,
|
|
367
370
|
"root": Object {
|
|
368
371
|
"child": Array [
|
|
369
372
|
Object {
|
|
@@ -465,6 +468,7 @@ describe('reporting', () => {
|
|
|
465
468
|
"nanos": 123000000,
|
|
466
469
|
"seconds": "1562203363",
|
|
467
470
|
},
|
|
471
|
+
"fieldExecutionWeight": 1,
|
|
468
472
|
"root": Object {
|
|
469
473
|
"child": Array [
|
|
470
474
|
Object {
|
|
@@ -568,6 +572,7 @@ describe('reporting', () => {
|
|
|
568
572
|
"nanos": 123000000,
|
|
569
573
|
"seconds": "1562203363",
|
|
570
574
|
},
|
|
575
|
+
"fieldExecutionWeight": 1,
|
|
571
576
|
"root": Object {
|
|
572
577
|
"child": Array [
|
|
573
578
|
Object {
|
|
@@ -184,7 +184,7 @@ describe('Using supergraphSdl dynamic configuration', () => {
|
|
|
184
184
|
const { state, compositionId } = gateway.__testing();
|
|
185
185
|
expect(state.phase).toEqual('loaded');
|
|
186
186
|
expect(compositionId).toEqual(
|
|
187
|
-
'
|
|
187
|
+
'1cb734eadae95dd1778f3fe3c9df6cbaecb95e80c2bcefed397c63ec72469032',
|
|
188
188
|
);
|
|
189
189
|
|
|
190
190
|
await gateway.stop();
|
|
@@ -209,7 +209,7 @@ describe('Using supergraphSdl dynamic configuration', () => {
|
|
|
209
209
|
const { state, compositionId } = gateway.__testing();
|
|
210
210
|
expect(state.phase).toEqual('loaded');
|
|
211
211
|
expect(compositionId).toEqual(
|
|
212
|
-
'
|
|
212
|
+
'1cb734eadae95dd1778f3fe3c9df6cbaecb95e80c2bcefed397c63ec72469032',
|
|
213
213
|
);
|
|
214
214
|
|
|
215
215
|
await expect(healthCheckCallback!(supergraphSdl)).resolves.toBeUndefined();
|
|
@@ -291,7 +291,7 @@ describe('Using supergraphSdl dynamic configuration', () => {
|
|
|
291
291
|
const { state, compositionId } = gateway.__testing();
|
|
292
292
|
expect(state.phase).toEqual('loaded');
|
|
293
293
|
expect(compositionId).toEqual(
|
|
294
|
-
'
|
|
294
|
+
'1cb734eadae95dd1778f3fe3c9df6cbaecb95e80c2bcefed397c63ec72469032',
|
|
295
295
|
);
|
|
296
296
|
|
|
297
297
|
await expect(healthCheckCallback!(supergraphSdl)).rejects.toThrowError(
|
|
@@ -297,7 +297,7 @@ it('prunes unfilled type conditions', async () => {
|
|
|
297
297
|
|
|
298
298
|
it('fetches interfaces returned from other services', async () => {
|
|
299
299
|
const query = `#graphql
|
|
300
|
-
query
|
|
300
|
+
query GetUserAndProductsWithPriceAndTitle {
|
|
301
301
|
me {
|
|
302
302
|
reviews {
|
|
303
303
|
product {
|
|
@@ -412,7 +412,7 @@ it('fetches interfaces returned from other services', async () => {
|
|
|
412
412
|
|
|
413
413
|
it('fetches composite fields from a foreign type casted to an interface [@provides field]', async () => {
|
|
414
414
|
const query = `#graphql
|
|
415
|
-
query
|
|
415
|
+
query GetUserAndProductsWithPriceAndName {
|
|
416
416
|
me {
|
|
417
417
|
reviews {
|
|
418
418
|
product {
|
|
@@ -624,7 +624,7 @@ it('allows for extending an interface from another service with fields', async (
|
|
|
624
624
|
describe('unions', () => {
|
|
625
625
|
it('handles unions from the same service', async () => {
|
|
626
626
|
const query = `#graphql
|
|
627
|
-
query
|
|
627
|
+
query GetUserAndProductsWithBrandInfo {
|
|
628
628
|
me {
|
|
629
629
|
reviews {
|
|
630
630
|
product {
|
|
@@ -443,15 +443,4 @@ describe('deprecation warnings', () => {
|
|
|
443
443
|
'The `schemaConfigDeliveryEndpoint` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent (array form) `uplinkEndpoints` configuration option.',
|
|
444
444
|
);
|
|
445
445
|
});
|
|
446
|
-
|
|
447
|
-
it('warns with `experimental_pollInterval` option set', async () => {
|
|
448
|
-
new ApolloGateway({
|
|
449
|
-
experimental_pollInterval: 10000,
|
|
450
|
-
logger,
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
expect(logger.warn).toHaveBeenCalledWith(
|
|
454
|
-
'The `experimental_pollInterval` option is deprecated and will be removed in a future version of `@apollo/gateway`. Please migrate to the equivalent `pollIntervalInMs` configuration option.',
|
|
455
|
-
);
|
|
456
|
-
});
|
|
457
446
|
});
|
|
@@ -6,7 +6,7 @@ expect.addSnapshotSerializer(astSerializer);
|
|
|
6
6
|
expect.addSnapshotSerializer(queryPlanSerializer);
|
|
7
7
|
it('supports passing additional fields defined by a requires', async () => {
|
|
8
8
|
const query = `#graphql
|
|
9
|
-
query
|
|
9
|
+
query GetReviewedBookNames {
|
|
10
10
|
me {
|
|
11
11
|
reviews {
|
|
12
12
|
product {
|
package/src/config.ts
CHANGED
|
@@ -81,6 +81,7 @@ export interface ServiceDefinitionUpdate {
|
|
|
81
81
|
export interface SupergraphSdlUpdate {
|
|
82
82
|
id: string;
|
|
83
83
|
supergraphSdl: string;
|
|
84
|
+
minDelaySeconds?: number;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
export function isSupergraphSdlUpdate(
|
|
@@ -125,11 +126,6 @@ interface GatewayConfigBase {
|
|
|
125
126
|
// experimental observability callbacks
|
|
126
127
|
experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
|
|
127
128
|
experimental_didUpdateSupergraph?: Experimental_DidUpdateSupergraphCallback;
|
|
128
|
-
/**
|
|
129
|
-
* @deprecated use `pollIntervalInMs` instead
|
|
130
|
-
*/
|
|
131
|
-
experimental_pollInterval?: number;
|
|
132
|
-
pollIntervalInMs?: number;
|
|
133
129
|
experimental_approximateQueryPlanStoreMiB?: number;
|
|
134
130
|
experimental_autoFragmentization?: boolean;
|
|
135
131
|
fetcher?: typeof fetch;
|
|
@@ -150,6 +146,7 @@ export interface ServiceListGatewayConfig extends GatewayConfigBase {
|
|
|
150
146
|
| ((
|
|
151
147
|
service: ServiceEndpointDefinition,
|
|
152
148
|
) => Promise<HeadersInit> | HeadersInit);
|
|
149
|
+
pollIntervalInMs?: number;
|
|
153
150
|
}
|
|
154
151
|
|
|
155
152
|
export interface ManagedGatewayConfig extends GatewayConfigBase {
|
|
@@ -168,6 +165,11 @@ export interface ManagedGatewayConfig extends GatewayConfigBase {
|
|
|
168
165
|
*/
|
|
169
166
|
uplinkEndpoints?: string[];
|
|
170
167
|
uplinkMaxRetries?: number;
|
|
168
|
+
/**
|
|
169
|
+
* @deprecated use `fallbackPollIntervalInMs` instead
|
|
170
|
+
*/
|
|
171
|
+
pollIntervalInMs?: number;
|
|
172
|
+
fallbackPollIntervalInMs?: number;
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
// TODO(trevor:removeServiceList): migrate users to `supergraphSdl` function option
|
|
@@ -176,6 +178,7 @@ interface ManuallyManagedServiceDefsGatewayConfig extends GatewayConfigBase {
|
|
|
176
178
|
* @deprecated: use `supergraphSdl` instead (either as a `SupergraphSdlHook` or `SupergraphManager`)
|
|
177
179
|
*/
|
|
178
180
|
experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions;
|
|
181
|
+
pollIntervalInMs?: number;
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
// TODO(trevor:removeServiceList): migrate users to `supergraphSdl` function option
|
|
@@ -185,6 +188,7 @@ interface ExperimentalManuallyManagedSupergraphSdlGatewayConfig
|
|
|
185
188
|
* @deprecated: use `supergraphSdl` instead (either as a `SupergraphSdlHook` or `SupergraphManager`)
|
|
186
189
|
*/
|
|
187
190
|
experimental_updateSupergraphSdl: Experimental_UpdateSupergraphSdl;
|
|
191
|
+
pollIntervalInMs?: number;
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
export function isManuallyManagedSupergraphSdlGatewayConfig(
|
|
@@ -238,7 +242,7 @@ type ManuallyManagedGatewayConfig =
|
|
|
238
242
|
| ManuallyManagedServiceDefsGatewayConfig
|
|
239
243
|
| ExperimentalManuallyManagedSupergraphSdlGatewayConfig
|
|
240
244
|
| ManuallyManagedSupergraphSdlGatewayConfig
|
|
241
|
-
// TODO(trevor:removeServiceList
|
|
245
|
+
// TODO(trevor:removeServiceList)
|
|
242
246
|
| ServiceListGatewayConfig;
|
|
243
247
|
|
|
244
248
|
// TODO(trevor:removeServiceList)
|
|
@@ -322,6 +326,7 @@ export function isManagedConfig(
|
|
|
322
326
|
return (
|
|
323
327
|
'schemaConfigDeliveryEndpoint' in config ||
|
|
324
328
|
'uplinkEndpoints' in config ||
|
|
329
|
+
'fallbackPollIntervalInMs' in config ||
|
|
325
330
|
(!isLocalConfig(config) &&
|
|
326
331
|
!isStaticSupergraphSdlConfig(config) &&
|
|
327
332
|
!isManuallyManagedConfig(config))
|
package/src/executeQueryPlan.ts
CHANGED
|
@@ -329,6 +329,7 @@ async function executeFetch<TContext>(
|
|
|
329
329
|
context,
|
|
330
330
|
fetch.operation,
|
|
331
331
|
variables,
|
|
332
|
+
fetch.operationName,
|
|
332
333
|
);
|
|
333
334
|
|
|
334
335
|
for (const entity of entities) {
|
|
@@ -364,6 +365,7 @@ async function executeFetch<TContext>(
|
|
|
364
365
|
context,
|
|
365
366
|
fetch.operation,
|
|
366
367
|
{...variables, representations},
|
|
368
|
+
fetch.operationName,
|
|
367
369
|
);
|
|
368
370
|
|
|
369
371
|
if (!dataReceivedFromService) {
|
|
@@ -405,6 +407,7 @@ async function executeFetch<TContext>(
|
|
|
405
407
|
context: ExecutionContext<TContext>,
|
|
406
408
|
source: string,
|
|
407
409
|
variables: Record<string, any>,
|
|
410
|
+
operationName: string | undefined,
|
|
408
411
|
): Promise<ResultMap | void | null> {
|
|
409
412
|
// We declare this as 'any' because it is missing url and method, which
|
|
410
413
|
// GraphQLRequest.http is supposed to have if it exists.
|
|
@@ -433,6 +436,7 @@ async function executeFetch<TContext>(
|
|
|
433
436
|
request: {
|
|
434
437
|
query: source,
|
|
435
438
|
variables,
|
|
439
|
+
operationName,
|
|
436
440
|
http,
|
|
437
441
|
},
|
|
438
442
|
incomingRequestContext: context.requestContext,
|
package/src/index.ts
CHANGED
|
@@ -202,8 +202,12 @@ export class ApolloGateway implements GraphQLService {
|
|
|
202
202
|
this.experimental_didUpdateSupergraph =
|
|
203
203
|
config?.experimental_didUpdateSupergraph;
|
|
204
204
|
|
|
205
|
-
this.
|
|
206
|
-
|
|
205
|
+
if (isManagedConfig(this.config)) {
|
|
206
|
+
this.pollIntervalInMs =
|
|
207
|
+
this.config.fallbackPollIntervalInMs ?? this.config.pollIntervalInMs;
|
|
208
|
+
} else if (isServiceListConfig(this.config)) {
|
|
209
|
+
this.pollIntervalInMs = this.config?.pollIntervalInMs;
|
|
210
|
+
}
|
|
207
211
|
|
|
208
212
|
this.issueConfigurationWarningsIfApplicable();
|
|
209
213
|
|
|
@@ -254,7 +258,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
254
258
|
'Polling Apollo services at a frequency of less than once per 10 ' +
|
|
255
259
|
'seconds (10000) is disallowed. Instead, the minimum allowed ' +
|
|
256
260
|
'pollInterval of 10000 will be used. Please reconfigure your ' +
|
|
257
|
-
'`
|
|
261
|
+
'`fallbackPollIntervalInMs` accordingly. If this is problematic for ' +
|
|
258
262
|
'your team, please contact support.',
|
|
259
263
|
);
|
|
260
264
|
}
|
|
@@ -288,9 +292,11 @@ export class ApolloGateway implements GraphQLService {
|
|
|
288
292
|
);
|
|
289
293
|
}
|
|
290
294
|
|
|
291
|
-
if ('
|
|
295
|
+
if (isManagedConfig(this.config) && 'pollIntervalInMs' in this.config) {
|
|
292
296
|
this.logger.warn(
|
|
293
|
-
'The `
|
|
297
|
+
'The `pollIntervalInMs` option is deprecated and will be removed in a future version of `@apollo/gateway`. ' +
|
|
298
|
+
'Please migrate to the equivalent `fallbackPollIntervalInMs` configuration option. ' +
|
|
299
|
+
'The poll interval is now defined by Uplink, this option will only be used if it is greater than the value defined by Uplink or as a fallback.',
|
|
294
300
|
);
|
|
295
301
|
}
|
|
296
302
|
}
|
|
@@ -404,7 +410,7 @@ export class ApolloGateway implements GraphQLService {
|
|
|
404
410
|
subgraphHealthCheck: this.config.serviceHealthCheck,
|
|
405
411
|
fetcher: this.fetcher,
|
|
406
412
|
logger: this.logger,
|
|
407
|
-
|
|
413
|
+
fallbackPollIntervalInMs: this.pollIntervalInMs ?? 10000,
|
|
408
414
|
}),
|
|
409
415
|
);
|
|
410
416
|
}
|
|
@@ -65,6 +65,7 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
65
65
|
compositionId: "originalId-1234",
|
|
66
66
|
maxRetries: 1,
|
|
67
67
|
roundRobinSeed: 0,
|
|
68
|
+
earliestFetchTime: null,
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
expect(result).toMatchObject({
|
|
@@ -88,6 +89,7 @@ describe('loadSupergraphSdlFromStorage', () => {
|
|
|
88
89
|
compositionId: "originalId-1234",
|
|
89
90
|
maxRetries: 1,
|
|
90
91
|
roundRobinSeed: 0,
|
|
92
|
+
earliestFetchTime: null,
|
|
91
93
|
}),
|
|
92
94
|
).rejects.toThrowError(
|
|
93
95
|
new UplinkFetcherError(
|
|
@@ -388,10 +390,49 @@ describe("loadSupergraphSdlFromUplinks", () => {
|
|
|
388
390
|
compositionId: "id-1234",
|
|
389
391
|
maxRetries: 5,
|
|
390
392
|
roundRobinSeed: 0,
|
|
393
|
+
earliestFetchTime: null,
|
|
391
394
|
});
|
|
392
395
|
|
|
393
396
|
expect(result).toBeNull();
|
|
394
397
|
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
395
398
|
});
|
|
399
|
+
|
|
400
|
+
it("Waits the correct time before retrying", async () => {
|
|
401
|
+
const timeoutSpy = jest.spyOn(global, 'setTimeout');
|
|
402
|
+
|
|
403
|
+
mockSupergraphSdlRequest('originalId-1234', mockCloudConfigUrl1).reply(500);
|
|
404
|
+
mockSupergraphSdlRequestIfAfter('originalId-1234', mockCloudConfigUrl2).reply(
|
|
405
|
+
200,
|
|
406
|
+
JSON.stringify({
|
|
407
|
+
data: {
|
|
408
|
+
routerConfig: {
|
|
409
|
+
__typename: 'RouterConfigResult',
|
|
410
|
+
id: 'originalId-1234',
|
|
411
|
+
supergraphSdl: getTestingSupergraphSdl()
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
}),
|
|
415
|
+
);
|
|
416
|
+
const fetcher = getDefaultFetcher();
|
|
417
|
+
|
|
418
|
+
await loadSupergraphSdlFromUplinks({
|
|
419
|
+
graphRef,
|
|
420
|
+
apiKey,
|
|
421
|
+
endpoints: [mockCloudConfigUrl1, mockCloudConfigUrl2],
|
|
422
|
+
errorReportingEndpoint: undefined,
|
|
423
|
+
fetcher: fetcher,
|
|
424
|
+
compositionId: "originalId-1234",
|
|
425
|
+
maxRetries: 1,
|
|
426
|
+
roundRobinSeed: 0,
|
|
427
|
+
earliestFetchTime: new Date(Date.now() + 1000),
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// test if setTimeout was called with a value in range to deal with time jitter
|
|
431
|
+
const setTimeoutCall = timeoutSpy.mock.calls[1][1];
|
|
432
|
+
expect(setTimeoutCall).toBeLessThanOrEqual(1000);
|
|
433
|
+
expect(setTimeoutCall).toBeGreaterThanOrEqual(900);
|
|
434
|
+
|
|
435
|
+
timeoutSpy.mockRestore();
|
|
436
|
+
});
|
|
396
437
|
});
|
|
397
438
|
|