Skip to content

Commit 1c03bd8

Browse files
committed
Merge branch 'main' into mh--update-introspection-and-fix-isPromise
2 parents bf4c0e2 + 89833ff commit 1c03bd8

File tree

11 files changed

+133
-48
lines changed

11 files changed

+133
-48
lines changed

docs/source/api/apollo-gateway.mdx

+15
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,21 @@ Provide this field **only** if you are using managed federation.
275275
</tr>
276276

277277

278+
<tr>
279+
<td>
280+
281+
###### `fallbackPollIntervalInMs` (managed mode only)
282+
283+
`number`
284+
</td>
285+
<td>
286+
287+
Specify this option as a fallback if Uplink fails to provide a polling interval. This will also take effect if `fallbackPollIntervalInMs` is greater than the Uplink defined interval.
288+
289+
</td>
290+
</tr>
291+
292+
278293
<tr>
279294
<td>
280295

gateway-js/CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The
66

77
> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section.
88
9-
- Correctly detect promises wrapped by proxies in entities resolver
9+
- Respect the `minDelaySeconds` returning from Uplink when polling and retrying to fetch the supergraph schema from Uplink [PR #1564](https://github.com/apollographql/federation/pull/1564)
10+
- Remove the previously deprecated `experimental_pollInterval` config option and deprecate `pollIntervalInMs` in favour of `fallbackPollIntervalInMs` (for managed mode only). [PR #1564](https://github.com/apollographql/federation/pull/1564)
11+
- Correctly detect promises wrapped by proxies in entities resolver [PR #1584](https://github.com/apollographql/federation/pull/1584)
1012

1113
## v2.0.0-preview.3
1214

gateway-js/src/__generated__/graphqlTypes.ts

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gateway-js/src/__tests__/gateway/lifecycle-hooks.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,15 @@ describe('lifecycle hooks', () => {
137137

138138
const [firstCall, secondCall] = mockDidUpdate.mock.calls;
139139

140-
// Note that we've composing our usual test fixtures here
140+
// Note that we've composing our usual test fixtures here
141141
const expectedFirstId = createHash('sha256').update(getTestingSupergraphSdl()).digest('hex');
142142
expect(firstCall[0]!.compositionId).toEqual(expectedFirstId);
143143
// first call should have no second "previous" argument
144144
expect(firstCall[1]).toBeUndefined();
145145

146146
// Note that this assertion is a tad fragile in that every time we modify
147147
// the supergraph (even just formatting differences), this ID will change
148-
// and this test will have to updated.
148+
// and this test will have to updated.
149149
expect(secondCall[0]!.compositionId).toEqual(
150150
'3ca7f295b11b070d1e1b56a698cbfabb70cb2b5912a4ff0ecae2fb91e8709838',
151151
);
@@ -183,7 +183,7 @@ describe('lifecycle hooks', () => {
183183
);
184184
});
185185

186-
it('registers schema change callbacks when experimental_pollInterval is set for unmanaged configs', async () => {
186+
it('registers schema change callbacks when pollIntervalInMs is set for unmanaged configs', async () => {
187187
const experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions =
188188
jest.fn(async (_config) => {
189189
return { serviceDefinitions, isNewSchema: true };

gateway-js/src/__tests__/integration/configuration.test.ts

-11
Original file line numberDiff line numberDiff line change
@@ -444,15 +444,4 @@ describe('deprecation warnings', () => {
444444
'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.',
445445
);
446446
});
447-
448-
it('warns with `experimental_pollInterval` option set', async () => {
449-
new ApolloGateway({
450-
experimental_pollInterval: 10000,
451-
logger,
452-
});
453-
454-
expect(logger.warn).toHaveBeenCalledWith(
455-
'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.',
456-
);
457-
});
458447
});

gateway-js/src/config.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export interface ServiceDefinitionUpdate {
8181
export interface SupergraphSdlUpdate {
8282
id: string;
8383
supergraphSdl: string;
84+
minDelaySeconds?: number;
8485
}
8586

8687
export function isSupergraphSdlUpdate(
@@ -125,11 +126,6 @@ interface GatewayConfigBase {
125126
// experimental observability callbacks
126127
experimental_didResolveQueryPlan?: Experimental_DidResolveQueryPlanCallback;
127128
experimental_didUpdateSupergraph?: Experimental_DidUpdateSupergraphCallback;
128-
/**
129-
* @deprecated use `pollIntervalInMs` instead
130-
*/
131-
experimental_pollInterval?: number;
132-
pollIntervalInMs?: number;
133129
experimental_approximateQueryPlanStoreMiB?: number;
134130
experimental_autoFragmentization?: boolean;
135131
fetcher?: typeof fetch;
@@ -150,6 +146,7 @@ export interface ServiceListGatewayConfig extends GatewayConfigBase {
150146
| ((
151147
service: ServiceEndpointDefinition,
152148
) => Promise<HeadersInit> | HeadersInit);
149+
pollIntervalInMs?: number;
153150
}
154151

155152
export interface ManagedGatewayConfig extends GatewayConfigBase {
@@ -168,6 +165,11 @@ export interface ManagedGatewayConfig extends GatewayConfigBase {
168165
*/
169166
uplinkEndpoints?: string[];
170167
uplinkMaxRetries?: number;
168+
/**
169+
* @deprecated use `fallbackPollIntervalInMs` instead
170+
*/
171+
pollIntervalInMs?: number;
172+
fallbackPollIntervalInMs?: number;
171173
}
172174

173175
// TODO(trevor:removeServiceList): migrate users to `supergraphSdl` function option
@@ -176,6 +178,7 @@ interface ManuallyManagedServiceDefsGatewayConfig extends GatewayConfigBase {
176178
* @deprecated: use `supergraphSdl` instead (either as a `SupergraphSdlHook` or `SupergraphManager`)
177179
*/
178180
experimental_updateServiceDefinitions: Experimental_UpdateServiceDefinitions;
181+
pollIntervalInMs?: number;
179182
}
180183

181184
// TODO(trevor:removeServiceList): migrate users to `supergraphSdl` function option
@@ -185,6 +188,7 @@ interface ExperimentalManuallyManagedSupergraphSdlGatewayConfig
185188
* @deprecated: use `supergraphSdl` instead (either as a `SupergraphSdlHook` or `SupergraphManager`)
186189
*/
187190
experimental_updateSupergraphSdl: Experimental_UpdateSupergraphSdl;
191+
pollIntervalInMs?: number;
188192
}
189193

190194
export function isManuallyManagedSupergraphSdlGatewayConfig(
@@ -238,7 +242,7 @@ type ManuallyManagedGatewayConfig =
238242
| ManuallyManagedServiceDefsGatewayConfig
239243
| ExperimentalManuallyManagedSupergraphSdlGatewayConfig
240244
| ManuallyManagedSupergraphSdlGatewayConfig
241-
// TODO(trevor:removeServiceList
245+
// TODO(trevor:removeServiceList)
242246
| ServiceListGatewayConfig;
243247

244248
// TODO(trevor:removeServiceList)
@@ -322,6 +326,7 @@ export function isManagedConfig(
322326
return (
323327
'schemaConfigDeliveryEndpoint' in config ||
324328
'uplinkEndpoints' in config ||
329+
'fallbackPollIntervalInMs' in config ||
325330
(!isLocalConfig(config) &&
326331
!isStaticSupergraphSdlConfig(config) &&
327332
!isManuallyManagedConfig(config))

gateway-js/src/index.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,12 @@ export class ApolloGateway implements GraphQLService {
200200
this.experimental_didUpdateSupergraph =
201201
config?.experimental_didUpdateSupergraph;
202202

203-
this.pollIntervalInMs =
204-
config?.pollIntervalInMs ?? config?.experimental_pollInterval;
203+
if (isManagedConfig(this.config)) {
204+
this.pollIntervalInMs =
205+
this.config.fallbackPollIntervalInMs ?? this.config.pollIntervalInMs;
206+
} else if (isServiceListConfig(this.config)) {
207+
this.pollIntervalInMs = this.config?.pollIntervalInMs;
208+
}
205209

206210
this.issueConfigurationWarningsIfApplicable();
207211

@@ -252,7 +256,7 @@ export class ApolloGateway implements GraphQLService {
252256
'Polling Apollo services at a frequency of less than once per 10 ' +
253257
'seconds (10000) is disallowed. Instead, the minimum allowed ' +
254258
'pollInterval of 10000 will be used. Please reconfigure your ' +
255-
'`pollIntervalInMs` accordingly. If this is problematic for ' +
259+
'`fallbackPollIntervalInMs` accordingly. If this is problematic for ' +
256260
'your team, please contact support.',
257261
);
258262
}
@@ -286,9 +290,11 @@ export class ApolloGateway implements GraphQLService {
286290
);
287291
}
288292

289-
if ('experimental_pollInterval' in this.config) {
293+
if (isManagedConfig(this.config) && 'pollIntervalInMs' in this.config) {
290294
this.logger.warn(
291-
'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.',
295+
'The `pollIntervalInMs` option is deprecated and will be removed in a future version of `@apollo/gateway`. ' +
296+
'Please migrate to the equivalent `fallbackPollIntervalInMs` configuration option. ' +
297+
'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.',
292298
);
293299
}
294300
}
@@ -406,7 +412,7 @@ export class ApolloGateway implements GraphQLService {
406412
subgraphHealthCheck: this.config.serviceHealthCheck,
407413
fetcher: this.fetcher,
408414
logger: this.logger,
409-
pollIntervalInMs: this.pollIntervalInMs ?? 10000,
415+
fallbackPollIntervalInMs: this.pollIntervalInMs ?? 10000,
410416
}),
411417
);
412418
}

gateway-js/src/supergraphManagers/UplinkFetcher/__tests__/loadSupergraphSdlFromStorage.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe('loadSupergraphSdlFromStorage', () => {
6565
compositionId: "originalId-1234",
6666
maxRetries: 1,
6767
roundRobinSeed: 0,
68+
earliestFetchTime: null,
6869
});
6970

7071
expect(result).toMatchObject({
@@ -88,6 +89,7 @@ describe('loadSupergraphSdlFromStorage', () => {
8889
compositionId: "originalId-1234",
8990
maxRetries: 1,
9091
roundRobinSeed: 0,
92+
earliestFetchTime: null,
9193
}),
9294
).rejects.toThrowError(
9395
new UplinkFetcherError(
@@ -388,10 +390,49 @@ describe("loadSupergraphSdlFromUplinks", () => {
388390
compositionId: "id-1234",
389391
maxRetries: 5,
390392
roundRobinSeed: 0,
393+
earliestFetchTime: null,
391394
});
392395

393396
expect(result).toBeNull();
394397
expect(fetcher).toHaveBeenCalledTimes(1);
395398
});
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+
});
396437
});
397438

gateway-js/src/supergraphManagers/UplinkFetcher/index.ts

+37-18
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { SubgraphHealthCheckFunction, SupergraphSdlUpdateFunction } from '../..'
66
import { loadSupergraphSdlFromUplinks } from './loadSupergraphSdlFromStorage';
77

88
export interface UplinkFetcherOptions {
9-
pollIntervalInMs: number;
9+
fallbackPollIntervalInMs: number;
1010
subgraphHealthCheck?: boolean;
1111
graphRef: string;
1212
apiKey: string;
@@ -31,6 +31,8 @@ export class UplinkFetcher implements SupergraphManager {
3131
process.env.APOLLO_OUT_OF_BAND_REPORTER_ENDPOINT ?? undefined;
3232
private compositionId?: string;
3333
private fetchCount: number = 0;
34+
private minDelayMs: number | null = null;
35+
private earliestFetchTime: Date | null = null;
3436

3537
constructor(options: UplinkFetcherOptions) {
3638
this.config = options;
@@ -46,7 +48,12 @@ export class UplinkFetcher implements SupergraphManager {
4648

4749
let initialSupergraphSdl: string | null = null;
4850
try {
49-
initialSupergraphSdl = await this.updateSupergraphSdl();
51+
const result = await this.updateSupergraphSdl();
52+
initialSupergraphSdl = result?.supergraphSdl || null;
53+
if (result?.minDelaySeconds) {
54+
this.minDelayMs = 1000 * result?.minDelaySeconds;
55+
this.earliestFetchTime = new Date(Date.now() + this.minDelayMs);
56+
}
5057
} catch (e) {
5158
this.logUpdateFailure(e);
5259
throw e;
@@ -83,6 +90,7 @@ export class UplinkFetcher implements SupergraphManager {
8390
compositionId: this.compositionId ?? null,
8491
maxRetries: this.config.maxRetries,
8592
roundRobinSeed: this.fetchCount++,
93+
earliestFetchTime: this.earliestFetchTime,
8694
});
8795

8896
if (!result) {
@@ -91,7 +99,8 @@ export class UplinkFetcher implements SupergraphManager {
9199
this.compositionId = result.id;
92100
// the healthCheck fn is only assigned if it's enabled in the config
93101
await this.healthCheck?.(result.supergraphSdl);
94-
return result.supergraphSdl;
102+
const { supergraphSdl, minDelaySeconds } = result;
103+
return { supergraphSdl, minDelaySeconds };
95104
}
96105
}
97106

@@ -101,24 +110,34 @@ export class UplinkFetcher implements SupergraphManager {
101110
}
102111

103112
private poll() {
104-
this.timerRef = setTimeout(async () => {
105-
if (this.state.phase === 'polling') {
106-
const pollingPromise = resolvable();
107-
108-
this.state.pollingPromise = pollingPromise;
109-
try {
110-
const maybeNewSupergraphSdl = await this.updateSupergraphSdl();
111-
if (maybeNewSupergraphSdl) {
112-
this.update?.(maybeNewSupergraphSdl);
113+
this.timerRef = setTimeout(
114+
async () => {
115+
if (this.state.phase === 'polling') {
116+
const pollingPromise = resolvable();
117+
118+
this.state.pollingPromise = pollingPromise;
119+
try {
120+
const result = await this.updateSupergraphSdl();
121+
const maybeNewSupergraphSdl = result?.supergraphSdl || null;
122+
if (result?.minDelaySeconds) {
123+
this.minDelayMs = 1000 * result?.minDelaySeconds;
124+
this.earliestFetchTime = new Date(Date.now() + this.minDelayMs);
125+
}
126+
if (maybeNewSupergraphSdl) {
127+
this.update?.(maybeNewSupergraphSdl);
128+
}
129+
} catch (e) {
130+
this.logUpdateFailure(e);
113131
}
114-
} catch (e) {
115-
this.logUpdateFailure(e);
132+
pollingPromise.resolve();
116133
}
117-
pollingPromise.resolve();
118-
}
119134

120-
this.poll();
121-
}, this.config.pollIntervalInMs);
135+
this.poll();
136+
},
137+
this.minDelayMs
138+
? Math.max(this.minDelayMs, this.config.fallbackPollIntervalInMs)
139+
: this.config.fallbackPollIntervalInMs,
140+
);
122141
}
123142

124143
private logUpdateFailure(e: any) {

0 commit comments

Comments
 (0)