Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
84ebed5
Update `DeferredJsonMerger` to take `pending` and `completed` into ac…
BoD Dec 13, 2024
e7e17ae
Track pending fragment ids rather than completed ones.
BoD Dec 16, 2024
3bc336a
Update more tests
BoD Dec 16, 2024
a466681
Add Apollo Server end-to-end tests
BoD Dec 16, 2024
f2b4179
Add a few more edge case tests
BoD Dec 17, 2024
6bb062d
Fix missed test
BoD Dec 17, 2024
891569f
Support appending lists in DeferredJsonMerger (for @stream)
BoD Jul 18, 2025
d5c8cb9
Rename DeferredJsonMerger -> IncrementalResultsMerger and DeferredFra…
BoD Jul 18, 2025
e327f81
Fix running tests hardcoded to true
BoD Jul 21, 2025
ad05aa0
Support both legacy and modern incremental delivery protocols, with a…
BoD Sep 24, 2025
eda7195
Simplify CI job
BoD Sep 25, 2025
bb1a83e
Do not expose accept headers in DefaultHttpRequestComposer's public API
BoD Sep 25, 2025
e608249
Make more symbols internal
BoD Sep 25, 2025
26399de
Mark IncrementalDeliveryProtocol @ApolloExperimental
BoD Sep 25, 2025
388c2ca
Rename Defer20220824 protocol to GraphQL17Alpha2 fpr consistency
BoD Sep 25, 2025
69ecb14
Keep symbols public but deprecated
BoD Sep 26, 2025
4d9b9f2
Let HttpNetworkTransport handle the Accept header
BoD Sep 26, 2025
77c52fb
Rename Defer20220824 protocol to GraphQL17Alpha2 for consistency
BoD Sep 26, 2025
5630434
Revert typealiases and use DeferredFragmentIdentifier again
BoD Sep 26, 2025
5249d9c
Revert handling incremental results in WebSocket
BoD Sep 26, 2025
eb20af6
Default to `application/graphql-response+json, application/json` in D…
BoD Sep 26, 2025
940f00f
Update package-lock.json
BoD Oct 15, 2025
8e6c7f5
Rename incremental delivery protocol enum/classes to DraftInitial and…
BoD Oct 15, 2025
8a77516
Update API dump
BoD Oct 16, 2025
44f84c4
Update package-lock.json
BoD Oct 16, 2025
40a9de7
Update the accept header with the value agreed upon
BoD Oct 16, 2025
13d7de0
Clarify comment
BoD Oct 16, 2025
6a4c28b
Rename incremental delivery protocol enums to V0_0 and V0_1
BoD Oct 16, 2025
97177bd
Fix API Dump after rebase
BoD Oct 16, 2025
97f29fb
Rename v0.0 -> v0.1 and v0.1 -> v0.2
BoD Oct 20, 2025
8ede194
Update API dump
BoD Oct 20, 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: defer-with-router-tests
name: defer-integration-tests

on:
schedule:
Expand All @@ -24,9 +24,23 @@ jobs:
- run: |
./router --supergraph tests/defer/router/simple-supergraph.graphqls &

- uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda #v3.4.2

- env:
DEFER_WITH_ROUTER_TESTS: true
run: |
./gradlew --no-daemon --console plain -p tests :defer:allTests
defer-with-apollo-server-tests:
runs-on: ubuntu-latest
if: github.repository == 'apollographql/apollo-kotlin'
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7

- working-directory: tests/defer/apollo-server/
run: |
npm install --legacy-peer-deps
npx patch-package
APOLLO_PORT=4000 npm start &

- env:
DEFER_WITH_APOLLO_SERVER_TESTS: true
run: |
./gradlew --no-daemon --console plain -p tests :defer:allTests
5 changes: 5 additions & 0 deletions libraries/apollo-api/api/apollo-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ public final class com/apollographql/apollo/api/DefaultUploadKt {
}

public final class com/apollographql/apollo/api/DeferredFragmentIdentifier {
public static final field Companion Lcom/apollographql/apollo/api/DeferredFragmentIdentifier$Companion;
public fun <init> (Ljava/util/List;Ljava/lang/String;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Ljava/lang/String;
Expand All @@ -542,6 +543,10 @@ public final class com/apollographql/apollo/api/DeferredFragmentIdentifier {
public fun toString ()Ljava/lang/String;
}

public final class com/apollographql/apollo/api/DeferredFragmentIdentifier$Companion {
public final fun getPending ()Lcom/apollographql/apollo/api/DeferredFragmentIdentifier;
}

public final class com/apollographql/apollo/api/EnumType : com/apollographql/apollo/api/CompiledNamedType {
public fun <init> (Ljava/lang/String;Ljava/util/List;)V
public final fun getValues ()Ljava/util/List;
Expand Down
5 changes: 5 additions & 0 deletions libraries/apollo-api/api/apollo-api.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,11 @@ final class com.apollographql.apollo.api/DeferredFragmentIdentifier { // com.apo
final fun equals(kotlin/Any?): kotlin/Boolean // com.apollographql.apollo.api/DeferredFragmentIdentifier.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.apollographql.apollo.api/DeferredFragmentIdentifier.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // com.apollographql.apollo.api/DeferredFragmentIdentifier.toString|toString(){}[0]

final object Companion { // com.apollographql.apollo.api/DeferredFragmentIdentifier.Companion|null[0]
final val Pending // com.apollographql.apollo.api/DeferredFragmentIdentifier.Companion.Pending|{}Pending[0]
final fun <get-Pending>(): com.apollographql.apollo.api/DeferredFragmentIdentifier // com.apollographql.apollo.api/DeferredFragmentIdentifier.Companion.Pending.<get-Pending>|<get-Pending>(){}[0]
}
}

final class com.apollographql.apollo.api/EnumType : com.apollographql.apollo.api/CompiledNamedType { // com.apollographql.apollo.api/EnumType|null[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ fun <T : Any> and(vararg other: BooleanExpression<T>): BooleanExpression<T> = Bo
fun <T : Any> not(other: BooleanExpression<T>): BooleanExpression<T> = BooleanExpression.Not(other)
fun variable(name: String): BooleanExpression<BVariable> = BooleanExpression.Element(BVariable(name))
fun label(label: String? = null): BooleanExpression<BLabel> = BooleanExpression.Element(BLabel(label))
fun possibleTypes(vararg typenames: String): BooleanExpression<BPossibleTypes> = BooleanExpression.Element(BPossibleTypes(typenames.toSet()))
fun possibleTypes(vararg typenames: String): BooleanExpression<BPossibleTypes> =
BooleanExpression.Element(BPossibleTypes(typenames.toSet()))

internal fun <T : Any> BooleanExpression<T>.evaluate(block: (T) -> Boolean): Boolean {
return when (this) {
Expand All @@ -74,18 +75,29 @@ fun BooleanExpression<BTerm>.evaluate(
return evaluate {
when (it) {
is BVariable -> !(variables?.contains(it.name) ?: false)
is BLabel -> hasDeferredFragment(deferredFragmentIdentifiers, croppedPath!!, it.label)
is BLabel -> shouldParseFragment(deferredFragmentIdentifiers, croppedPath!!, it.label)
is BPossibleTypes -> it.possibleTypes.contains(typename)
}
}
}

private fun hasDeferredFragment(deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>?, path: List<Any>, label: String?): Boolean {
private fun shouldParseFragment(deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>?, path: List<Any>, label: String?): Boolean {
if (deferredFragmentIdentifiers == null) {
// By default, parse all deferred fragments - this is the case when parsing from the normalized cache.
return true
}
return deferredFragmentIdentifiers.contains(DeferredFragmentIdentifier(path, label))
val identifier = DeferredFragmentIdentifier(path, label)
return if (deferredFragmentIdentifiers.isPending()) {
// Modern (v0.1 draft) protocol: parse fragments that are _not_ pending
!deferredFragmentIdentifiers.contains(identifier)
} else {
// Legacy (v0.0 draft) protocol: parse fragments that have been merged
deferredFragmentIdentifiers.contains(identifier)
}
}

private fun Set<DeferredFragmentIdentifier>.isPending(): Boolean {
return any { it === DeferredFragmentIdentifier.Pending }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CustomScalarAdapters private constructor(
@JvmField
val falseVariables: Set<String>?,
/**
* Defer identifiers used to determine whether the parser must parse @defer fragments
* Identifiers used to determine whether the parser must parse deferred fragments
*/
@JvmField
val deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>?,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.apollographql.apollo.api

import com.apollographql.apollo.annotations.ApolloInternal

data class DeferredFragmentIdentifier(
/**
* Path of the fragment in the overall JSON response. The elements can either be Strings (names) or Integers (array indices).
*/
val path: List<Any>,
val label: String?,
)
) {
companion object {
/**
* Special identifier to signal that the identifiers are pending, as in the modern version of the protocol.
*/
@ApolloInternal
val Pending = DeferredFragmentIdentifier(emptyList(), "__pending")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.apollographql.apollo.api.Operation
import com.apollographql.apollo.api.Query
import com.apollographql.apollo.api.Subscription
import com.apollographql.apollo.api.Upload
import com.apollographql.apollo.api.http.DefaultHttpRequestComposer.Companion.composePostParams
import com.apollographql.apollo.api.http.internal.urlEncode
import com.apollographql.apollo.api.json.BufferedSinkJsonWriter
import com.apollographql.apollo.api.json.JsonWriter
Expand Down Expand Up @@ -38,24 +39,30 @@ import okio.buffer
*/
class DefaultHttpRequestComposer(
private val serverUrl: String,
private val enablePostCaching: Boolean
private val enablePostCaching: Boolean,
) : HttpRequestComposer {

constructor(serverUrl: String): this(serverUrl, false)
constructor(serverUrl: String) : this(serverUrl, false)

override fun <D : Operation.Data> compose(apolloRequest: ApolloRequest<D>): HttpRequest {
val operation = apolloRequest.operation
val customScalarAdapters = apolloRequest.executionContext[CustomScalarAdapters] ?: CustomScalarAdapters.Empty

val requestHeaders = mutableListOf<HttpHeader>().apply {
if (apolloRequest.operation is Subscription<*>) {
add(HttpHeader(HEADER_ACCEPT_NAME, HEADER_ACCEPT_VALUE_MULTIPART))
} else {
add(HttpHeader(HEADER_ACCEPT_NAME, HEADER_ACCEPT_VALUE_DEFER))
}
if (apolloRequest.httpHeaders != null) {
addAll(apolloRequest.httpHeaders)
}
if (get("accept") == null) {
add(
HttpHeader("accept",
if (apolloRequest.operation is Subscription<*>) {
"multipart/mixed;subscriptionSpec=1.0, application/graphql-response+json, application/json"
} else {
"application/graphql-response+json, application/json"
}
)
)
}
}

val sendApqExtensions = apolloRequest.sendApqExtensions ?: false
Expand Down Expand Up @@ -119,14 +126,18 @@ class DefaultHttpRequestComposer(
// and thus is safe to execute.
// See https://www.apollographql.com/docs/apollo-server/security/cors/#preventing-cross-site-request-forgery-csrf
// for details.
internal val HEADER_APOLLO_REQUIRE_PREFLIGHT = "Apollo-Require-Preflight"
private const val HEADER_APOLLO_REQUIRE_PREFLIGHT = "Apollo-Require-Preflight"

@Deprecated("This was made public by mistake and will be removed in a future version, please use your own constants instead")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0)
val HEADER_ACCEPT_NAME = "Accept"

// TODO The deferSpec=20220824 part is a temporary measure so early backend implementations of the @defer directive
// can recognize early client implementations and potentially reply in a compatible way.
// This should be removed in later versions.
@Deprecated("This was made public by mistake and will be removed in a future version, please use your own constants instead")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0)
val HEADER_ACCEPT_VALUE_DEFER = "multipart/mixed;deferSpec=20220824, application/graphql-response+json, application/json"

@Deprecated("This was made public by mistake and will be removed in a future version, please use your own constants instead")
@ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v5_0_0)
val HEADER_ACCEPT_VALUE_MULTIPART = "multipart/mixed;subscriptionSpec=1.0, application/graphql-response+json, application/json"

private fun <D : Operation.Data> buildGetUrl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal object ResponseParser {
val falseVariables = operation.falseVariables(customScalarAdapters)
data = operation.parseData(jsonReader, customScalarAdapters, falseVariables, deferredFragmentIds, errors)
}

"errors" -> errors = jsonReader.readErrors()
"extensions" -> extensions = jsonReader.readAny() as? Map<String, Any?>
else -> {
Expand Down Expand Up @@ -100,7 +101,8 @@ private fun JsonReader.readError(): Error {


@Suppress("DEPRECATION")
return Error.Builder(message = message).locations(locations).path(path).extensions(extensions).nonStandardFields(nonStandardFields).build()
return Error.Builder(message = message).locations(locations).path(path).extensions(extensions).nonStandardFields(nonStandardFields)
.build()
}

private fun JsonReader.readPath(): List<Any>? {
Expand Down Expand Up @@ -164,4 +166,4 @@ fun JsonReader.readErrors(): List<Error> {
}
endArray()
return list
}
}
15 changes: 13 additions & 2 deletions libraries/apollo-runtime/api/android/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ public final class com/apollographql/apollo/interceptor/RetryOnErrorInterceptorK
public static final fun RetryOnErrorInterceptor (Lcom/apollographql/apollo/network/NetworkMonitor;)Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
}

public final class com/apollographql/apollo/network/IncrementalDeliveryProtocol : java/lang/Enum {
public static final field V0_1 Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;
public static final field V0_2 Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;
public static fun values ()[Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;
}

public abstract interface class com/apollographql/apollo/network/NetworkMonitor : java/io/Closeable {
public abstract fun isOnline ()Lkotlinx/coroutines/flow/StateFlow;
}
Expand Down Expand Up @@ -326,7 +334,7 @@ public abstract interface class com/apollographql/apollo/network/http/HttpInterc
}

public final class com/apollographql/apollo/network/http/HttpNetworkTransport : com/apollographql/apollo/network/NetworkTransport {
public synthetic fun <init> (Lcom/apollographql/apollo/api/http/HttpRequestComposer;Lcom/apollographql/apollo/network/http/HttpEngine;Ljava/util/List;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcom/apollographql/apollo/api/http/HttpRequestComposer;Lcom/apollographql/apollo/network/http/HttpEngine;Ljava/util/List;ZLcom/apollographql/apollo/internal/incremental/IncrementalDeliveryProtocolImpl;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun dispose ()V
public fun execute (Lcom/apollographql/apollo/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow;
public final fun getInterceptors ()Ljava/util/List;
Expand All @@ -342,6 +350,7 @@ public final class com/apollographql/apollo/network/http/HttpNetworkTransport$Bu
public final fun httpEngine (Lcom/apollographql/apollo/network/http/HttpEngine;)Lcom/apollographql/apollo/network/http/HttpNetworkTransport$Builder;
public final fun httpHeaders (Ljava/util/List;)Lcom/apollographql/apollo/network/http/HttpNetworkTransport$Builder;
public final fun httpRequestComposer (Lcom/apollographql/apollo/api/http/HttpRequestComposer;)Lcom/apollographql/apollo/network/http/HttpNetworkTransport$Builder;
public final fun incrementalDeliveryProtocol (Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;)Lcom/apollographql/apollo/network/http/HttpNetworkTransport$Builder;
public final fun interceptors (Ljava/util/List;)Lcom/apollographql/apollo/network/http/HttpNetworkTransport$Builder;
public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/network/http/HttpNetworkTransport$Builder;
}
Expand Down Expand Up @@ -522,6 +531,7 @@ public final class com/apollographql/apollo/network/websocket/WebSocketNetworkTr
public final fun build ()Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport;
public final fun connectionAcknowledgeTimeout-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder;
public final fun idleTimeout-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder;
public final fun incrementalDeliveryProtocol (Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder;
public final fun parserFactory (Lcom/apollographql/apollo/network/websocket/SubscriptionParserFactory;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder;
public final fun pingInterval-BwNAW2A (Lkotlin/time/Duration;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder;
public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/network/websocket/WebSocketNetworkTransport$Builder;
Expand Down Expand Up @@ -650,7 +660,7 @@ public final class com/apollographql/apollo/network/ws/WebSocketEngineKt {
}

public final class com/apollographql/apollo/network/ws/WebSocketNetworkTransport : com/apollographql/apollo/network/NetworkTransport {
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Ljava/util/List;Lcom/apollographql/apollo/network/ws/WebSocketEngine;JLcom/apollographql/apollo/network/ws/WsProtocol$Factory;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Ljava/util/List;Lcom/apollographql/apollo/network/ws/WebSocketEngine;JLcom/apollographql/apollo/network/ws/WsProtocol$Factory;Lkotlin/jvm/functions/Function3;Lcom/apollographql/apollo/internal/incremental/IncrementalDeliveryProtocolImpl;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun closeConnection (Ljava/lang/Throwable;)V
public fun dispose ()V
public fun execute (Lcom/apollographql/apollo/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow;
Expand All @@ -664,6 +674,7 @@ public final class com/apollographql/apollo/network/ws/WebSocketNetworkTransport
public final fun build ()Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport;
public final fun headers (Ljava/util/List;)Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport$Builder;
public final fun idleTimeoutMillis (J)Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport$Builder;
public final fun incrementalDeliveryProtocol (Lcom/apollographql/apollo/network/IncrementalDeliveryProtocol;)Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport$Builder;
public final fun protocol (Lcom/apollographql/apollo/network/ws/WsProtocol$Factory;)Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport$Builder;
public final fun reopenWhen (Lkotlin/jvm/functions/Function3;)Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport$Builder;
public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/network/ws/WebSocketNetworkTransport$Builder;
Expand Down
Loading
Loading