Skip to content
Open
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
9274bb7
DeMorgan's Law
jerelmiller Sep 24, 2025
922da17
Compare against no defer spec
jerelmiller Sep 24, 2025
0bb58a0
Add new multipart mixed header
jerelmiller Sep 24, 2025
9b63f24
Update request error message to suggest new format
jerelmiller Sep 24, 2025
7a8292c
Add Alpha2 suffix to existing incremental types
jerelmiller Sep 24, 2025
852a6a6
Add Alpha2 suffix to polyfill file
jerelmiller Sep 24, 2025
8ac5c71
Add polyfill types for alpha9
jerelmiller Sep 24, 2025
96112c0
Export alpha9 types
jerelmiller Sep 24, 2025
56245aa
Remove ordering of incremental fields
jerelmiller Sep 24, 2025
92fa99b
Add type for GraphQLResponseBody with alpha 9 format
jerelmiller Sep 24, 2025
fbfa0df
Update writeMultipartBody signature
jerelmiller Sep 24, 2025
c7abd87
Update payload type for willSendSubsequentPayload
jerelmiller Sep 24, 2025
4d3beb0
Load from alpha 9
jerelmiller Sep 24, 2025
d6e19f6
Add alpha2 suffix to existing execute types and add alpha9 types
jerelmiller Sep 24, 2025
ddddd79
Update ApolloServer types
jerelmiller Sep 24, 2025
bb827ce
Update constructor types
jerelmiller Sep 24, 2025
0da6d92
Ensure executeIncrementally returns alpha9 types
jerelmiller Sep 24, 2025
d8fa3ec
Handle formatting alpha9 errors
jerelmiller Sep 24, 2025
bd8d0c3
Throw if version mismatch from header
jerelmiller Sep 25, 2025
1916a46
Simplify check
jerelmiller Sep 25, 2025
5ec9b5e
Rename media types
jerelmiller Sep 25, 2025
5aa7578
Change value of alpha9 spec version
jerelmiller Sep 25, 2025
48e2572
Change output header depending on graphql version
jerelmiller Sep 25, 2025
c2403d5
Swap order of conditional
jerelmiller Sep 25, 2025
b7ce974
Enable incremental delivery tests for alpha9
jerelmiller Sep 25, 2025
5c7bed1
Wrap existing tests in check for alpha2
jerelmiller Sep 25, 2025
7df7889
Add back field ordering
jerelmiller Sep 25, 2025
758f50f
Enable incremental delivery tests for alpha9
jerelmiller Sep 25, 2025
c6566c4
Reverse demorgan's
jerelmiller Sep 25, 2025
e34f911
Extract variable
jerelmiller Sep 25, 2025
7b1f7af
Update test
jerelmiller Sep 25, 2025
6437c0e
Fix condition
jerelmiller Sep 25, 2025
14c9263
Combine conditionals
jerelmiller Sep 25, 2025
b3469ab
Use valid media type value for response
jerelmiller Sep 25, 2025
e486ca1
DeMorgan's law
jerelmiller Sep 25, 2025
c77e9f0
Fix incorrect header in test
jerelmiller Sep 25, 2025
ec029b0
Move test
jerelmiller Sep 25, 2025
c8932db
Fix typo
jerelmiller Sep 25, 2025
016fbb9
Tweak error message
jerelmiller Sep 25, 2025
caada27
Use describe instead
jerelmiller Sep 25, 2025
0610c85
Add tests for alpha.9
jerelmiller Sep 25, 2025
7b8d287
Fix incorrect assertions for alpha.9
jerelmiller Sep 25, 2025
ecd31cb
Update test for alpha.9
jerelmiller Sep 25, 2025
fa0c4ee
Update another test to work with alpha.9
jerelmiller Sep 25, 2025
fc8c3a6
Update test in apolloServerTests for alpha.9
jerelmiller Sep 25, 2025
f5965e1
Update test description
jerelmiller Sep 25, 2025
109c4a0
Fix incorrect assertion
jerelmiller Sep 25, 2025
84862b9
Update smoke test for alpha.9
jerelmiller Sep 25, 2025
c0d24bd
Add basic tests for @stream
jerelmiller Sep 25, 2025
756456a
Ordering
jerelmiller Sep 25, 2025
80f06e9
Ensure tests are skipped instead of hidden
jerelmiller Sep 25, 2025
5184e4c
Revert quotes
jerelmiller Sep 25, 2025
586b102
Add changesets
jerelmiller Sep 25, 2025
93534d0
Update docs
jerelmiller Sep 25, 2025
d865b57
Fix typo
jerelmiller Sep 25, 2025
62e1cca
Add @yaacovcr/transform as optional peer dep
jerelmiller Sep 29, 2025
ef8da6c
Install transform in circle
jerelmiller Sep 29, 2025
601cf6a
Add flag to load legacy incremental
jerelmiller Sep 29, 2025
426026d
Remove check for graphql alpha.2
jerelmiller Sep 29, 2025
45f35c1
Prefer alpha.9 format
jerelmiller Sep 29, 2025
ece5991
Don't error with incompatible graphql.js version
jerelmiller Sep 29, 2025
e8a6d0a
Remove checks for versions in tests
jerelmiller Sep 29, 2025
6c005a7
Update error message
jerelmiller Sep 29, 2025
01ac540
Track legacyExecute and executeIncrementally in separate vars
jerelmiller Sep 29, 2025
6ffc9aa
Use legacy format when header used
jerelmiller Sep 29, 2025
023425b
Update error snapshots
jerelmiller Sep 29, 2025
18175de
Rename test descriptions
jerelmiller Sep 29, 2025
aff5045
Update apolloServerTests to remove check on version
jerelmiller Sep 29, 2025
b151633
Only run alpha.9 tests in circleci
jerelmiller Sep 29, 2025
025d292
Update smoke test to check both versions
jerelmiller Sep 29, 2025
a17ffff
Fix ref in circle config
jerelmiller Sep 29, 2025
1ad2066
Add ts-ignore
jerelmiller Sep 29, 2025
6f1e3e6
Throw if old format requested but not supported
jerelmiller Sep 29, 2025
b7e1661
Install @yaacovcr/transform in prepare.sh
jerelmiller Sep 29, 2025
0f8d32d
Add yaacovcr to cspell dict
jerelmiller Sep 29, 2025
1d101dc
Fix typo
jerelmiller Sep 29, 2025
13f005c
Update changeset
jerelmiller Sep 29, 2025
a7cbae6
Add another changeset
jerelmiller Sep 29, 2025
f697a2c
Update documentation
jerelmiller Sep 29, 2025
ef1938d
Fix typo
jerelmiller Sep 29, 2025
18a9868
Use && instead of ||
jerelmiller Sep 29, 2025
fc889d8
Remove ordering on incremental fields
jerelmiller Oct 1, 2025
ce4d8d1
Allow v17.0.0-alpha.9
jerelmiller Oct 1, 2025
c9c8c78
Combine all changesets
jerelmiller Oct 1, 2025
28e9d9c
Fix typo
jerelmiller Oct 1, 2025
b32394a
Grammar
jerelmiller Oct 1, 2025
3c0a88e
Remove use of "modern" in changeset
jerelmiller Oct 1, 2025
bd5cd63
Revert changes to peer deps
jerelmiller Oct 1, 2025
96af552
Don't export unneeded types
jerelmiller Oct 1, 2025
5d15554
Update error messages
jerelmiller Oct 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fruity-ways-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/server': minor
---

Add 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; incrementalDeliverySpec=3283f8a` to specify the new format. If the `Accept` header is not compatible with the installed version of `graphql`, an error is returned to the client.
38 changes: 38 additions & 0 deletions .changeset/yellow-worlds-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
'@apollo/server': minor
---

Add `[email protected]` incremental delivery types and rename the existing incremental delivery types by adding an `Alpha2` suffix. If you import these types in your code, you will need to add the `Alpha2` suffix.

```diff
import type {
- GraphQLExperimentalFormattedInitialIncrementalExecutionResult,
+ GraphQLExperimentalFormattedInitialIncrementalExecutionResultAlpha2,

- GraphQLExperimentalFormattedSubsequentIncrementalExecutionResult,
+ GraphQLExperimentalFormattedSubsequentIncrementalExecutionResultAlpha2,

- GraphQLExperimentalFormattedIncrementalResult,
+ GraphQLExperimentalFormattedIncrementalResultAlpha2,

- GraphQLExperimentalFormattedIncrementalDeferResult,
+ GraphQLExperimentalFormattedIncrementalDeferResultAlpha2,

- GraphQLExperimentalFormattedIncrementalStreamResult,
+ GraphQLExperimentalFormattedIncrementalStreamResultAlpha2,
} from '@apollo/server';
```

Incremental delivery types for the `[email protected]` version are now available using the `Alpha9` suffix:

```ts
import type {
GraphQLExperimentalFormattedInitialIncrementalExecutionResultAlpha9,
GraphQLExperimentalFormattedSubsequentIncrementalExecutionResultAlpha9,
GraphQLExperimentalFormattedIncrementalResultAlpha9,
GraphQLExperimentalFormattedIncrementalDeferResultAlpha9,
GraphQLExperimentalFormattedIncrementalStreamResultAlpha9,
GraphQLExperimentalFormattedCompletedResultAlpha9,
GraphQLExperimentalPendingResultAlpha9,
} from '@apollo/server';
```
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ jobs:
docker:
- image: cimg/base:stable
environment:
INCREMENTAL_DELIVERY_TESTS_ENABLED: t
GRAPHQL_JS_VERSION: 17.0.0-alpha.9
steps:
- setup-node:
Expand Down
6 changes: 3 additions & 3 deletions docs/source/workflow/requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ For more details, see [the CSRF prevention documentation](../security/cors#preve

## Incremental delivery (experimental)

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]` 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.
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]` or `[email protected]` 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.

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:

Expand Down Expand Up @@ -119,8 +119,8 @@ const schema = new GraphQLSchema({
});
```

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.
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 (for `17.0.0-alpha.2`) or `incrementalDeliverySpec` parameter (for `17.0.0-alpha.9`). Specifically, clients prepared to accept incremental delivery responses should send an `accept` header like `multipart/mixed; deferSpec=20220824` or `multipart/mixed; incrementalDeliverySpec=3283f8a`. 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.

> Apollo Server *only* supports the specific pre-release `[email protected]`. 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.
> Apollo Server *only* supports the specific pre-release `[email protected]` or `graphql@17.0.0-alpha.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.2` or `17.0.0-alpha.9`.

You cannot combine [batching](#batching) with incremental delivery in the same request.
80 changes: 67 additions & 13 deletions packages/integration-testsuite/src/apolloServerTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type FieldNode,
type GraphQLFormattedError,
GraphQLScalarType,
version as graphqlVersion,
} from 'graphql';

// Note that by doing deep imports here we don't need to install React.
Expand Down Expand Up @@ -1178,8 +1179,11 @@ export function defineIntegrationTestSuiteApolloServerTests(
expect(Object.keys(reports[0].tracesPerQuery)[0]).toMatch(/^# -\n/);
});

(process.env.INCREMENTAL_DELIVERY_TESTS_ENABLED ? it : it.skip)(
'includes all fields with defer',
(process.env.INCREMENTAL_DELIVERY_TESTS_ENABLED &&
graphqlVersion === '17.0.0-alpha.2'
? it
: it.skip)(
'includes all fields with defer [email protected]',
async () => {
await setupApolloServerAndFetchPair({}, {}, [], true);
const response = await fetch(uri, {
Expand All @@ -1199,18 +1203,68 @@ export function defineIntegrationTestSuiteApolloServerTests(
`"multipart/mixed; boundary="-"; deferSpec=20220824"`,
);
expect(await response.text()).toMatchInlineSnapshot(`
"
---
content-type: application/json; charset=utf-8

{"hasNext":true,"data":{"justAField":"a string"}}
---
content-type: application/json; charset=utf-8
"
---
content-type: application/json; charset=utf-8

{"hasNext":true,"data":{"justAField":"a string"}}
---
content-type: application/json; charset=utf-8

{"hasNext":false,"incremental":[{"path":[],"data":{"delayedFoo":{"bar":"hi"}}}]}
-----
"
`);
const reports = await reportIngress.promiseOfReports;
expect(reports.length).toBe(1);
expect(Object.keys(reports[0].tracesPerQuery)).toHaveLength(1);
const trace = Object.values(reports[0].tracesPerQuery)[0]
.trace?.[0] as Trace;
expect(trace).toBeDefined();
expect(trace?.root?.child?.[0].responseName).toBe('justAField');
expect(trace?.root?.child?.[1].responseName).toBe('delayedFoo');
expect(trace?.root?.child?.[1].child?.[0].responseName).toBe(
'bar',
);
},
);

{"hasNext":false,"incremental":[{"path":[],"data":{"delayedFoo":{"bar":"hi"}}}]}
-----
"
`);
(process.env.INCREMENTAL_DELIVERY_TESTS_ENABLED &&
graphqlVersion === '17.0.0-alpha.9'
? it
: it.skip)(
'includes all fields with defer [email protected]',
async () => {
await setupApolloServerAndFetchPair({}, {}, [], true);
const response = await fetch(uri, {
method: 'POST',
headers: {
'content-type': 'application/json',
accept: 'multipart/mixed; incrementalDeliverySpec=3283f8a',
},
body: JSON.stringify({
query: '{ justAField ...@defer { delayedFoo { bar} } }',
}),
});
expect(response.status).toBe(200);
expect(
response.headers.get('content-type'),
).toMatchInlineSnapshot(
`"multipart/mixed; boundary="-"; incrementalDeliverySpec=3283f8a"`,
);
expect(await response.text()).toMatchInlineSnapshot(`
"
---
content-type: application/json; charset=utf-8

{"hasNext":true,"data":{"justAField":"a string"},"pending":[{"id":"0","path":[]}]}
---
content-type: application/json; charset=utf-8

{"hasNext":false,"incremental":[{"id":"0","data":{"delayedFoo":{"bar":"hi"}}}],"completed":[{"id":"0"}]}
-----
"
`);
const reports = await reportIngress.promiseOfReports;
expect(reports.length).toBe(1);
expect(Object.keys(reports[0].tracesPerQuery)).toHaveLength(1);
Expand Down
Loading