Skip to content

Commit 80a1a1a

Browse files
jerelmillerglasser
andauthored
Add support for newer incremental delivery format (#8148)
Closes #7932 Adds support for the `[email protected]` `@defer` and `@stream` incremental delivery protocol. When `[email protected]` is installed, clients must send the `Accept` header with a value of `multipart/mixed; incrementalSpec=v0.2` to specify the new format. If the `Accept` header is not compatible with the installed version of `graphql` (such as sending `deferSpec=20220824` when ``17.0.0-alpha.9` is installed), an error is returned to the client. --------- Co-authored-by: David Glasser <[email protected]>
1 parent 6978fe0 commit 80a1a1a

File tree

19 files changed

+1213
-311
lines changed

19 files changed

+1213
-311
lines changed

.changeset/fruity-ways-tell.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
'@apollo/server': minor
3+
---
4+
5+
Apollo Server now supports the incremental delivery protocol (`@defer` and `@stream`) that ships with `[email protected]`. To use the current protocol, clients must send the `Accept` header with a value of `multipart/mixed; incrementalSpec=v0.2`.
6+
7+
Upgrading to 5.1 will depend on what version of `graphql` you have installed and whether you already support the incremental delivery protocol.
8+
9+
## I use `graphql@16` without incremental delivery
10+
11+
Continue using `graphql` v16 with no additional changes. Incremental delivery won't be available.
12+
13+
## I use `graphql@16` but would like to add support for incremental delivery
14+
15+
Install `[email protected]` and follow the ["Incremental delivery" guide](https://www.apollographql.com/docs/apollo-server/workflow/requests#incremental-delivery-experimental) to add the `@defer` and `@stream` directives to your schema. Clients should send the `Accept` header with a value of `multipart/mixed; incrementalSpec=v0.2` to get multipart responses.
16+
17+
## I use `[email protected]` and use incremental delivery
18+
19+
You must upgrade to `[email protected]` to continue using incremental delivery. If you'd like to continue providing support for the legacy incremental protocol, install the [`@yaacovcr/transform`](https://github.com/yaacovCR/transform) package. Apollo Server will attempt to load this module when the client specifies an `Accept` header with a value of `multipart/mixed; deferSpec=20220824`. If this package is not installed, an error is returned by the server.
20+
21+
Because Apollo Server now supports multiple versions of the incremental delivery types, the existing incremental delivery types have been renamed with an `Alpha2` suffix. If you import these types in your code, you will need to add the `Alpha2` suffix.
22+
23+
```diff
24+
import type {
25+
- GraphQLExperimentalFormattedInitialIncrementalExecutionResult,
26+
+ GraphQLExperimentalFormattedInitialIncrementalExecutionResultAlpha2,
27+
28+
- GraphQLExperimentalFormattedSubsequentIncrementalExecutionResult,
29+
+ GraphQLExperimentalFormattedSubsequentIncrementalExecutionResultAlpha2,
30+
31+
- GraphQLExperimentalFormattedIncrementalResult,
32+
+ GraphQLExperimentalFormattedIncrementalResultAlpha2,
33+
34+
- GraphQLExperimentalFormattedIncrementalDeferResult,
35+
+ GraphQLExperimentalFormattedIncrementalDeferResultAlpha2,
36+
37+
- GraphQLExperimentalFormattedIncrementalStreamResult,
38+
+ GraphQLExperimentalFormattedIncrementalStreamResultAlpha2,
39+
} from '@apollo/server';
40+
```
41+
42+
Incremental delivery types for the `[email protected]` version are now available using the `Alpha9` suffix:
43+
44+
```ts
45+
import type {
46+
GraphQLExperimentalFormattedInitialIncrementalExecutionResultAlpha9,
47+
GraphQLExperimentalFormattedSubsequentIncrementalExecutionResultAlpha9,
48+
GraphQLExperimentalFormattedIncrementalResultAlpha9,
49+
GraphQLExperimentalFormattedIncrementalDeferResultAlpha9,
50+
GraphQLExperimentalFormattedIncrementalStreamResultAlpha9,
51+
GraphQLExperimentalFormattedCompletedResultAlpha9,
52+
GraphQLExperimentalPendingResultAlpha9,
53+
} from '@apollo/server';
54+
```

.circleci/config.yml

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,37 +60,22 @@ jobs:
6060
- setup-node
6161
- run: npm run test:smoke
6262

63-
Full incremental delivery tests with graphql 17 alpha 2:
63+
Full incremental delivery tests with graphql 17 alpha 9:
6464
docker:
6565
- image: cimg/base:stable
6666
environment:
6767
INCREMENTAL_DELIVERY_TESTS_ENABLED: t
68-
GRAPHQL_JS_VERSION: 17.0.0-alpha.2
68+
GRAPHQL_JS_VERSION: 17.0.0-alpha.9
6969
steps:
7070
- setup-node:
7171
node-version: "20"
7272
# Install a prerelease of graphql-js 17 with incremental delivery support.
7373
# --legacy-peer-deps because nothing expects v17 yet.
74-
- run: npm i --legacy-peer-deps "graphql@${GRAPHQL_JS_VERSION}"
74+
- run: npm i --legacy-peer-deps "graphql@${GRAPHQL_JS_VERSION}" @yaacovcr/transform
7575
- run: npm run test:ci
7676
- maybe-upload-coverage
7777
- run: npm run test:smoke
7878

79-
Test with recent graphql-js alpha:
80-
docker:
81-
- image: cimg/base:stable
82-
environment:
83-
GRAPHQL_JS_VERSION: 17.0.0-alpha.9
84-
steps:
85-
- setup-node:
86-
node-version: "20"
87-
# Install a newer prerelease of graphql-js 17; we do not yet support
88-
# its incremental delivery format.
89-
# --legacy-peer-deps because nothing expects v17 yet.
90-
- run: npm i --legacy-peer-deps "graphql@${GRAPHQL_JS_VERSION}"
91-
- run: npm run test:ci
92-
- run: npm run test:smoke
93-
9479
Prettier:
9580
docker:
9681
- image: cimg/base:stable
@@ -172,8 +157,7 @@ workflows:
172157
- Spell check
173158
- Codegen check
174159
- Smoke test built package
175-
- Full incremental delivery tests with graphql 17 alpha 2
176-
- Test with recent graphql-js alpha
160+
- Full incremental delivery tests with graphql 17 alpha 9
177161
- Changesets
178162
security-scans:
179163
jobs:

cspell-dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,5 @@ whatwg
221221
Wheelock's
222222
withrequired
223223
xorby
224+
yaacovcr
224225
YOURNAME

docs/source/workflow/requests.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ For more details, see [the CSRF prevention documentation](../security/cors#preve
9191

9292
## Incremental delivery (experimental)
9393

94-
Incremental delivery is a [Stage 2: Draft Proposal](https://github.com/graphql/graphql-spec/pull/742) to the GraphQL specification which adds `@defer` and `@stream` executable directives. These directives allow clients to specify that parts of an operation can be sent after an initial response, so that slower fields do not delay all other fields. As of June 2025, the `graphql` library (also known as `graphql-js`) upon which Apollo Server is built implements incremental delivery only in the unreleased major version 17. If a pre-release of `[email protected].2` is installed in your server, Apollo Server can execute these incremental delivery directives and provide streaming [`multipart/mixed`](https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md) responses.
94+
Incremental delivery is a [Stage 1: Proposal](https://github.com/graphql/graphql-spec/pull/1110) to the GraphQL specification which adds `@defer` and `@stream` executable directives. These directives allow clients to specify that parts of an operation can be sent after an initial response, so that slower fields do not delay all other fields. As of June 2025, the `graphql` library (also known as `graphql-js`) upon which Apollo Server is built implements incremental delivery only in the unreleased major version 17. If a pre-release of `[email protected].9` is installed in your server, Apollo Server can execute these incremental delivery directives and provide streaming [`multipart/mixed`](https://github.com/graphql/graphql-over-http/blob/main/rfcs/IncrementalDelivery.md) responses.
9595

9696
Support for incremental delivery in graphql version 17 is [opt-in](https://github.com/robrichard/defer-stream-wg/discussions/12), meaning the directives are not defined by default. In order to use `@defer` or `@stream`, you must provide the appropriate definition(s) in your SDL. The definitions below can be pasted into your schema as-is:
9797

@@ -119,8 +119,30 @@ const schema = new GraphQLSchema({
119119
});
120120
```
121121

122-
Clients sending operations with incremental delivery directives need to explicitly indicate that they are expecting to receive `multipart/mixed` responses in an `accept` header. Moreover, because incremental delivery has not yet been finalized in the GraphQL spec and may change before the final version, they need to specify that they expect the particular response format that Apollo Server produces today via a `deferSpec` parameter. Specifically, clients prepared to accept incremental delivery responses should send an `accept` header like `multipart/mixed; deferSpec=20220824`. Note that this header implies that *only* multipart responses should be accepted; typically, clients will send an accept header like `multipart/mixed; deferSpec=20220824, application/json` indicating that either multipart or single-part responses are acceptable.
122+
Clients sending operations with incremental delivery directives need to explicitly indicate that they are expecting to receive `multipart/mixed` responses in an `accept` header. Moreover, because incremental delivery has not yet been finalized in the GraphQL spec and may change before the final version, they need to specify that they expect the particular response format that Apollo Server produces today via a `incrementalSpec` parameter. Specifically, clients prepared to accept incremental delivery responses should send an `accept` header like `multipart/mixed; incrementalSpec=v0.2`. Note that this header implies that *only* multipart responses should be accepted; typically, clients will send an accept header like `multipart/mixed; incrementalSpec=v0.2, application/json` indicating that either multipart or single-part responses are acceptable.
123123

124-
> Apollo Server *only* supports the specific pre-release `[email protected].2`. Newer alpha versions of `graphql` v17 support a slightly different format for incremental delivery, which Apollo Server does not yet support. Apollo Server 5 checks the version of `graphql` you have installed and will not attempt to support incremental delivery unless it is precisely `17.0.0-alpha.2`. We hope to support the newer incremental delivery protocol in a future release, using a different `deferSpec` value.
124+
> Apollo Server *only* supports the specific pre-release `[email protected].9`. Apollo Server 5 checks the version of `graphql` you have installed and will not attempt to support incremental delivery unless it is precisely `17.0.0-alpha.9`.
125125
126126
You cannot combine [batching](#batching) with incremental delivery in the same request.
127+
128+
<Note>
129+
130+
Apollo Server 5.1 changed the required pre-release `graphql` version from `17.0.0-alpha.2` to `17.0.0-alpha.9`. If you are using 5.0 or below, use `graphql` version `17.0.0-alpha.2` instead.
131+
132+
</Note>
133+
134+
<MinVersion version="5.1.0">
135+
136+
### Add support for the legacy incremental delivery protocol
137+
138+
</MinVersion>
139+
140+
Clients may request the legacy incremental delivery protocol by specifying an `accept` header with a value of `multipart/mixed; deferSpec=20220824`. Apollo Server does not support the legacy incremental delivery protocol by default and an error will be returned to the client.
141+
142+
You may choose to support the legacy incremental delivery protocol by installing the [`@yaacovcr/transform` package](https://github.com/yaacovCR/transform) which provides the needed utilities to format the incremental result using the legacy protocol.
143+
144+
```sh
145+
npm install @yaacovcr/transform
146+
```
147+
148+
There is nothing else to configure. Apollo Server will load the necessary utility if this package is installed.

packages/integration-testsuite/src/apolloServerTests.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ export function defineIntegrationTestSuiteApolloServerTests(
11791179
});
11801180

11811181
(process.env.INCREMENTAL_DELIVERY_TESTS_ENABLED ? it : it.skip)(
1182-
'includes all fields with defer',
1182+
'includes all fields with defer legacy',
11831183
async () => {
11841184
await setupApolloServerAndFetchPair({}, {}, [], true);
11851185
const response = await fetch(uri, {
@@ -1203,11 +1203,58 @@ export function defineIntegrationTestSuiteApolloServerTests(
12031203
---
12041204
content-type: application/json; charset=utf-8
12051205
1206-
{"hasNext":true,"data":{"justAField":"a string"}}
1206+
{"data":{"justAField":"a string"},"hasNext":true}
12071207
---
12081208
content-type: application/json; charset=utf-8
12091209
1210-
{"hasNext":false,"incremental":[{"path":[],"data":{"delayedFoo":{"bar":"hi"}}}]}
1210+
{"hasNext":false,"incremental":[{"data":{"delayedFoo":{"bar":"hi"}},"path":[]}]}
1211+
-----
1212+
"
1213+
`);
1214+
const reports = await reportIngress.promiseOfReports;
1215+
expect(reports.length).toBe(1);
1216+
expect(Object.keys(reports[0].tracesPerQuery)).toHaveLength(1);
1217+
const trace = Object.values(reports[0].tracesPerQuery)[0]
1218+
.trace?.[0] as Trace;
1219+
expect(trace).toBeDefined();
1220+
expect(trace?.root?.child?.[0].responseName).toBe('justAField');
1221+
expect(trace?.root?.child?.[1].responseName).toBe('delayedFoo');
1222+
expect(trace?.root?.child?.[1].child?.[0].responseName).toBe(
1223+
'bar',
1224+
);
1225+
},
1226+
);
1227+
1228+
(process.env.INCREMENTAL_DELIVERY_TESTS_ENABLED ? it : it.skip)(
1229+
'includes all fields with defer modern',
1230+
async () => {
1231+
await setupApolloServerAndFetchPair({}, {}, [], true);
1232+
const response = await fetch(uri, {
1233+
method: 'POST',
1234+
headers: {
1235+
'content-type': 'application/json',
1236+
accept: 'multipart/mixed; incrementalSpec=v0.2',
1237+
},
1238+
body: JSON.stringify({
1239+
query: '{ justAField ...@defer { delayedFoo { bar} } }',
1240+
}),
1241+
});
1242+
expect(response.status).toBe(200);
1243+
expect(
1244+
response.headers.get('content-type'),
1245+
).toMatchInlineSnapshot(
1246+
`"multipart/mixed; boundary="-"; incrementalSpec=v0.2"`,
1247+
);
1248+
expect(await response.text()).toMatchInlineSnapshot(`
1249+
"
1250+
---
1251+
content-type: application/json; charset=utf-8
1252+
1253+
{"data":{"justAField":"a string"},"pending":[{"id":"0","path":[]}],"hasNext":true}
1254+
---
1255+
content-type: application/json; charset=utf-8
1256+
1257+
{"hasNext":false,"incremental":[{"data":{"delayedFoo":{"bar":"hi"}},"id":"0"}],"completed":[{"id":"0"}]}
12111258
-----
12121259
"
12131260
`);

0 commit comments

Comments
 (0)