Skip to content

Commit 51acbeb

Browse files
authored
Require legacy incremental execute as an ApolloServer option instead of a dynamic import (#8161)
Fixes #8159 Dynamically importing `@yaacovcr/transform` if available has proven to be a difficult solution because some bundlers fail to build when the package isn't installed. This PR addresses this issue by making the legacy execute function an option that can be provided to the `ApolloServer` constructor. This means no direct dependency on `@yaacovcr/transform` and makes it possible to use other legacy execution functions if available. To migrate, import the `legacyExecuteIncrementally` and provide it as the `legacyExperimentalExecuteIncrementally` option to the `ApolloServer` constructor. ```ts import { legacyExecuteIncrementally } from '@yaacovcr/transform'; const server = new ApolloServer({ // ... legacyExperimentalExecuteIncrementally: legacyExecuteIncrementally }) ```
1 parent ae1be29 commit 51acbeb

File tree

12 files changed

+124
-36
lines changed

12 files changed

+124
-36
lines changed

.changeset/chatty-states-write.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@apollo/server': minor
3+
---
4+
5+
Fix an issue where some bundlers would fail to build because of the dynamic import for the optional peer dependency on `@yaacovcr/transform` introduced in `@apollo/server` 5.1.0. To provide support for the legacy incremental format, you must now provide the `legacyExperimentalExecuteIncrementally` option to the `ApolloServer` constructor.
6+
7+
```ts
8+
import { legacyExecuteIncrementally } from '@yaacovcr/transform';
9+
10+
const server = new ApolloServer({
11+
// ...
12+
legacyExperimentalExecuteIncrementally: legacyExecuteIncrementally
13+
})
14+
```
15+
16+
If the `legacyExperimentalExecuteIncrementally` option is not provided and the client sends an `Accept` header with a value of `multipart/mixed; deferSpec=20220824`, an error is returned by the server.

docs/source/workflow/requests.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Apollo Server 5.1 changed the required pre-release `graphql` version from `17.0.
131131

132132
</Note>
133133

134-
<MinVersion version="5.1.0">
134+
<MinVersion version="5.2.0">
135135

136136
### Add support for the legacy incremental delivery protocol
137137

@@ -145,4 +145,13 @@ You may choose to support the legacy incremental delivery protocol by installing
145145
npm install @yaacovcr/transform
146146
```
147147

148-
There is nothing else to configure. Apollo Server will load the necessary utility if this package is installed.
148+
You will then need to configure Apollo Server with the `legacyExperimentalExecuteIncrementally` option in order to provide support for the legacy incremental format.
149+
150+
```ts
151+
import { legacyExecuteIncrementally } from '@yaacovcr/transform';
152+
153+
const server = new ApolloServer({
154+
// ...
155+
legacyExperimentalExecuteIncrementally: legacyExecuteIncrementally
156+
})
157+
```

packages/integration-testsuite/src/apolloServerTests.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,19 @@ export function defineIntegrationTestSuiteApolloServerTests(
11811181
(process.env.INCREMENTAL_DELIVERY_TESTS_ENABLED ? it : it.skip)(
11821182
'includes all fields with defer legacy',
11831183
async () => {
1184-
await setupApolloServerAndFetchPair({}, {}, [], true);
1184+
const { legacyExecuteIncrementally } = await import(
1185+
// @ts-ignore might not be installed
1186+
'@yaacovcr/transform'
1187+
);
1188+
await setupApolloServerAndFetchPair(
1189+
{},
1190+
{
1191+
legacyExperimentalExecuteIncrementally:
1192+
legacyExecuteIncrementally,
1193+
},
1194+
[],
1195+
true,
1196+
);
11851197
const response = await fetch(uri, {
11861198
method: 'POST',
11871199
headers: {

packages/integration-testsuite/src/httpServerTests.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2466,7 +2466,16 @@ export function defineIntegrationTestSuiteHttpServerTests(
24662466
['multipart/mixed; deferSpec=20220824, application/json'],
24672467
['application/json, multipart/mixed; deferSpec=20220824'],
24682468
])('basic @defer working with accept: %s', async (accept) => {
2469-
const app = await createApp({ typeDefs, resolvers });
2469+
const { legacyExecuteIncrementally } = await import(
2470+
// @ts-ignore might not be installed
2471+
'@yaacovcr/transform'
2472+
);
2473+
const app = await createApp({
2474+
typeDefs,
2475+
resolvers,
2476+
legacyExperimentalExecuteIncrementally:
2477+
legacyExecuteIncrementally,
2478+
});
24702479
const res = await request(app)
24712480
.post('/')
24722481
.set('accept', accept)
@@ -2497,6 +2506,10 @@ content-type: application/json; charset=utf-8\r
24972506
['multipart/mixed; deferSpec=20220824, application/json'],
24982507
['application/json, multipart/mixed; deferSpec=20220824'],
24992508
])('basic @stream working with accept: %s', async (accept) => {
2509+
const { legacyExecuteIncrementally } = await import(
2510+
// @ts-ignore might not be installed
2511+
'@yaacovcr/transform'
2512+
);
25002513
const app = await createApp({
25012514
typeDefs: `#graphql
25022515
directive @stream(if: Boolean! = true, label: String, initialCount: Int! = 0) on FIELD
@@ -2518,6 +2531,8 @@ content-type: application/json; charset=utf-8\r
25182531
},
25192532
},
25202533
},
2534+
legacyExperimentalExecuteIncrementally:
2535+
legacyExecuteIncrementally,
25212536
});
25222537
const res = await request(app)
25232538
.post('/')
@@ -2557,7 +2572,16 @@ content-type: application/json; charset=utf-8\r
25572572
});
25582573

25592574
it('first payload sent while deferred field is blocking', async () => {
2560-
const app = await createApp({ typeDefs, resolvers });
2575+
const { legacyExecuteIncrementally } = await import(
2576+
// @ts-ignore might not be installed
2577+
'@yaacovcr/transform'
2578+
);
2579+
const app = await createApp({
2580+
typeDefs,
2581+
resolvers,
2582+
legacyExperimentalExecuteIncrementally:
2583+
legacyExecuteIncrementally,
2584+
});
25612585
const gotFirstChunkBarrier = resolvable();
25622586
const resPromise = request(app)
25632587
.post('/')

packages/server/src/ApolloServer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { ApolloServerErrorCode } from './errors/index.js';
3636
import type { ApolloServerOptionsWithStaticSchema } from './externalTypes/constructor.js';
3737
import type {
3838
ExecuteOperationOptions,
39+
LegacyExperimentalExecuteIncrementally,
3940
VariableValues,
4041
} from './externalTypes/graphql.js';
4142
import type {
@@ -171,6 +172,7 @@ export interface ApolloServerInternals<TContext extends BaseContext> {
171172
stringifyResult: (
172173
value: FormattedExecutionResult,
173174
) => string | Promise<string>;
175+
legacyExperimentalExecuteIncrementally?: LegacyExperimentalExecuteIncrementally;
174176
}
175177

176178
function defaultLogger(): Logger {
@@ -350,6 +352,8 @@ export class ApolloServer<in out TContext extends BaseContext = BaseContext> {
350352
__testing_incrementalExecutionResults:
351353
config.__testing_incrementalExecutionResults,
352354
stringifyResult: config.stringifyResult ?? prettyJSONStringify,
355+
legacyExperimentalExecuteIncrementally:
356+
config.legacyExperimentalExecuteIncrementally,
353357
};
354358

355359
this.warnAgainstDeprecatedConfigOptions(config);

packages/server/src/externalTypes/constructor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import type {
1818
import type { KeyValueCache } from '@apollo/utils.keyvaluecache';
1919
import type { GatewayInterface } from '@apollo/server-gateway-interface';
2020
import type { ApolloServerPlugin } from './plugins.js';
21-
import type { BaseContext } from './index.js';
21+
import type {
22+
BaseContext,
23+
LegacyExperimentalExecuteIncrementally,
24+
} from './index.js';
2225
import type {
2326
GraphQLExperimentalIncrementalExecutionResultsAlpha2,
2427
GraphQLExperimentalIncrementalExecutionResultsAlpha9,
@@ -121,6 +124,8 @@ interface ApolloServerOptionsBase<TContext extends BaseContext> {
121124
*/
122125
status400ForVariableCoercionErrors?: boolean;
123126

127+
legacyExperimentalExecuteIncrementally?: LegacyExperimentalExecuteIncrementally;
128+
124129
// For testing only.
125130
__testing_incrementalExecutionResults?:
126131
| GraphQLExperimentalIncrementalExecutionResultsAlpha2

packages/server/src/externalTypes/graphql.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
* `executeHTTPGraphQLRequest` and `executeOperation`. The
55
* `responseForOperation` plugin hook also returns a `GraphQLResponse`.
66
*/
7-
import type { FormattedExecutionResult } from 'graphql';
7+
import type {
8+
ExecutionArgs,
9+
ExecutionResult,
10+
FormattedExecutionResult,
11+
} from 'graphql';
812
import type { BaseContext } from './context.js';
913
import type { HTTPGraphQLHead, HTTPGraphQLRequest } from './http.js';
1014
import type { WithRequired } from '@apollo/utils.withrequired';
@@ -16,6 +20,7 @@ import type {
1620
GraphQLExperimentalFormattedInitialIncrementalExecutionResultAlpha9,
1721
GraphQLExperimentalFormattedSubsequentIncrementalExecutionResultAlpha9,
1822
} from './incrementalDeliveryPolyfillAlpha9.js';
23+
import { type GraphQLExperimentalIncrementalExecutionResultsAlpha2 } from '../incrementalDeliveryPolyfill.js';
1924

2025
export interface GraphQLRequest<
2126
TVariables extends VariableValues = VariableValues,
@@ -62,3 +67,15 @@ export type GraphQLResponse<TData = Record<string, unknown>> = WithRequired<
6267
export interface ExecuteOperationOptions<TContext extends BaseContext> {
6368
contextValue?: TContext;
6469
}
70+
71+
type PromiseOrValue<T> = Promise<T> | T;
72+
73+
export type LegacyExperimentalExecuteIncrementally<
74+
TData = Record<string, unknown>,
75+
TExtensions = Record<string, unknown>,
76+
> = (
77+
args: ExecutionArgs,
78+
) => PromiseOrValue<
79+
| ExecutionResult<TData, TExtensions>
80+
| GraphQLExperimentalIncrementalExecutionResultsAlpha2<TData, TExtensions>
81+
>;

packages/server/src/externalTypes/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
* intention (it's public API).
66
*/
77
export type { BaseContext, ContextFunction, ContextThunk } from './context.js';
8-
export type { GraphQLRequest, GraphQLResponse } from './graphql.js';
8+
export type {
9+
GraphQLRequest,
10+
GraphQLResponse,
11+
LegacyExperimentalExecuteIncrementally,
12+
} from './graphql.js';
913
export type {
1014
HTTPGraphQLRequest,
1115
HTTPGraphQLResponse,

packages/server/src/incrementalDeliveryPolyfill.ts

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from 'graphql';
77
import { BadRequestError } from './internalErrorClasses.js';
88
import { MEDIA_TYPES } from './ApolloServer.js';
9+
import { type LegacyExperimentalExecuteIncrementally } from './externalTypes/graphql.js';
910

1011
// This file "polyfills" graphql@17's experimentalExecuteIncrementally (by
1112
// returning a function that does not understand incremental directives if
@@ -191,30 +192,8 @@ let graphqlExperimentalExecuteIncrementally:
191192
| null
192193
| undefined = undefined;
193194

194-
let legacyExecuteIncrementally:
195-
| ((
196-
args: ExecutionArgs,
197-
) => PromiseOrValue<
198-
ExecutionResult | GraphQLExperimentalIncrementalExecutionResultsAlpha2
199-
>)
200-
| null
201-
| undefined;
202-
203-
async function tryToLoadLegacyExecuteIncrementally() {
204-
try {
205-
// @ts-ignore `@yaacovcr/transform` is an optional peer dependency
206-
const transform = await import('@yaacovcr/transform');
207-
legacyExecuteIncrementally = transform.legacyExecuteIncrementally;
208-
} catch {
209-
legacyExecuteIncrementally = null;
210-
}
211-
}
212-
213195
async function tryToLoadGraphQL17() {
214-
if (
215-
graphqlExperimentalExecuteIncrementally !== undefined &&
216-
legacyExecuteIncrementally !== undefined
217-
) {
196+
if (graphqlExperimentalExecuteIncrementally !== undefined) {
218197
return;
219198
}
220199

@@ -223,8 +202,6 @@ async function tryToLoadGraphQL17() {
223202
graphql.version === '17.0.0-alpha.9' &&
224203
'experimentalExecuteIncrementally' in graphql
225204
) {
226-
await tryToLoadLegacyExecuteIncrementally();
227-
228205
graphqlExperimentalExecuteIncrementally = (graphql as any)
229206
.experimentalExecuteIncrementally;
230207
} else {
@@ -234,17 +211,23 @@ async function tryToLoadGraphQL17() {
234211

235212
export async function executeIncrementally({
236213
useLegacyIncremental,
214+
legacyExperimentalExecuteIncrementally,
237215
...args
238-
}: ExecutionArgs & { useLegacyIncremental?: boolean }): Promise<
216+
}: ExecutionArgs & {
217+
useLegacyIncremental?: boolean;
218+
legacyExperimentalExecuteIncrementally:
219+
| LegacyExperimentalExecuteIncrementally
220+
| undefined;
221+
}): Promise<
239222
| ExecutionResult
240223
| GraphQLExperimentalIncrementalExecutionResultsAlpha2
241224
| GraphQLExperimentalIncrementalExecutionResultsAlpha9
242225
> {
243226
await tryToLoadGraphQL17();
244227

245228
if (useLegacyIncremental) {
246-
if (legacyExecuteIncrementally) {
247-
return legacyExecuteIncrementally(args);
229+
if (legacyExperimentalExecuteIncrementally) {
230+
return legacyExperimentalExecuteIncrementally(args);
248231
}
249232

250233
// Only throw if the server supports incremental delivery with the new

packages/server/src/requestPipeline.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ export async function processGraphQLRequest<TContext extends BaseContext>(
584584
operationName: request.operationName,
585585
fieldResolver: internals.fieldResolver,
586586
useLegacyIncremental,
587+
legacyExperimentalExecuteIncrementally:
588+
internals.legacyExperimentalExecuteIncrementally,
587589
});
588590
if ('initialResult' in resultOrResults) {
589591
return {

0 commit comments

Comments
 (0)