Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
df2321e
Always apply streaming result so that errors that bubble to defer bou…
jerelmiller Jul 31, 2026
5f08c11
Add changeset
jerelmiller Jul 31, 2026
b6b0121
Add failing test for defer with network-only and errors returned
jerelmiller Jul 31, 2026
5a8e528
Update comment
jerelmiller Aug 3, 2026
fc58179
Add test to validate stream with complete errors truncates cache list
jerelmiller Aug 3, 2026
7f2aa4c
Fix issue when defer boundary is delivered after its marked completed
jerelmiller Aug 3, 2026
d53e0fe
Add additional tests for incremental to ensure it prunes another case…
jerelmiller Aug 3, 2026
b143dcc
Add getPendingWithInfo to track whether pending item was delivered or…
jerelmiller Aug 3, 2026
4471bfa
Remove getPendingType method
jerelmiller Aug 3, 2026
c5d565d
Remove pending property from request object
jerelmiller Aug 3, 2026
a5b22b5
Add changesets
jerelmiller Aug 3, 2026
38d52de
Update api report
jerelmiller Aug 3, 2026
dccc472
Add failing test for defer with errorPolicy: "none"
jerelmiller Aug 3, 2026
14e6eed
Add incrementalHandler property
jerelmiller Aug 3, 2026
86b5e4a
Add public access to streamInfo
jerelmiller Aug 3, 2026
f2cc981
Access streamInfo from helper
jerelmiller Aug 3, 2026
95bab66
Combine conditionals
jerelmiller Aug 3, 2026
e37478e
Truncate data with none error policy
jerelmiller Aug 3, 2026
2f4d41c
Unconditionally set dataState on the result after merging with increm…
jerelmiller Aug 3, 2026
9e7aa48
Fix dataState with errorPolicy: "none" when error bubbles to defer bo…
jerelmiller Aug 3, 2026
1ea4918
Add failing test for wrong partial dataState
jerelmiller Aug 3, 2026
0163e06
Compare hasNext instead of streamInfo in shouldWrite
jerelmiller Aug 3, 2026
7ed55a7
Remove unused import
jerelmiller Aug 3, 2026
a79d205
Update api report
jerelmiller Aug 3, 2026
c1a32cc
Fix lint errors/warnings
jerelmiller Aug 3, 2026
d1c5573
Add test for no emit on mid-stream complete
jerelmiller Aug 3, 2026
f366a0d
Add test to guard against partial abandoned query
jerelmiller Aug 3, 2026
d3cdc0e
Remove unneeded cache-first
jerelmiller Aug 3, 2026
fc168f9
Update api report
jerelmiller Aug 3, 2026
fbef270
Change as any cast to different location
jerelmiller Aug 3, 2026
fd47624
Fix bad tsdoc comment syntax
jerelmiller Aug 3, 2026
cfe0a19
Mark streamInfo and getPendingWithInfo as internal
jerelmiller Aug 3, 2026
afeff3f
Mark concrete class with internal
jerelmiller Aug 3, 2026
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
4 changes: 2 additions & 2 deletions .api-reports/api-report-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1377,8 +1377,8 @@ export const windowFocusSource: RefetchEventManager.EventSource<Event>;
// Warnings were encountered during analysis:
//
// src/core/ApolloClient.ts:635:5 - (ae-forgotten-export) The symbol "NextFetchPolicyContext" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:375:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
// src/core/QueryManager.ts:195:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:376:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
// src/core/QueryManager.ts:196:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
69 changes: 65 additions & 4 deletions .api-reports/api-report-incremental.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import type { DocumentNode } from 'graphql';
import type { FormattedExecutionResult } from 'graphql';
import type { GraphQLFormattedError } from 'graphql';
import type { HKT } from '@apollo/client/utilities';
import type { Incremental as Incremental_2 } from '@apollo/client/incremental';
import { StreamInfoTrie as StreamInfoTrie_2 } from '@apollo/client/utilities/internal';
import type { Trie } from '@wry/trie';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

// @public (undocumented)
namespace Defer20220824Handler {
Expand Down Expand Up @@ -197,16 +200,32 @@ export namespace Incremental {
// (undocumented)
export interface IncrementalRequest<Chunk extends Record<string, unknown>, TData> {
// (undocumented)
getPendingType?: (id: string) => "defer" | "stream";
getPendingWithInfo?: () => Array<PendingItemWithInfo>;
// (undocumented)
handle: (cacheData: TData | DeepPartial<TData> | undefined | null, chunk: Chunk) => FormattedExecutionResult<TData>;
// (undocumented)
hasNext: boolean;
// Warning: (ae-forgotten-export) The symbol "StreamInfoTrie" needs to be exported by the entry point index.d.ts
//
// (undocumented)
pending?: Array<Incremental.PendingResult>;
readonly streamInfo?: StreamInfoTrie;
}
// (undocumented)
export type Path = ReadonlyArray<string | number>;
// @internal @deprecated (undocumented)
export interface PendingDeferResultWithInfo {
// (undocumented)
delivered: boolean;
// (undocumented)
path: Incremental.Path;
// (undocumented)
type: "defer";
}
// Warning: (ae-incompatible-release-tags) The symbol "PendingItemWithInfo" is marked as @public, but its signature references "Incremental" which is marked as @internal
// Warning: (ae-incompatible-release-tags) The symbol "PendingItemWithInfo" is marked as @public, but its signature references "Incremental" which is marked as @internal
//
// (undocumented)
export type PendingItemWithInfo = PendingDeferResultWithInfo | PendingStreamResultWithInfo;
// (undocumented)
export interface PendingResult {
// (undocumented)
Expand All @@ -217,6 +236,13 @@ export namespace Incremental {
path: Incremental.Path;
}
// @internal @deprecated (undocumented)
export interface PendingStreamResultWithInfo {
// (undocumented)
path: Incremental.Path;
// (undocumented)
type: "stream";
}
// @internal @deprecated (undocumented)
export interface StartRequestOptions {
// (undocumented)
query: DocumentNode;
Expand All @@ -233,13 +259,21 @@ export namespace Incremental {
// @public (undocumented)
class IncrementalRequest<TData> implements Incremental.IncrementalRequest<GraphQL17Alpha9Handler.Chunk<TData>, TData> {
// (undocumented)
getPendingType(id: string): "defer" | "stream";
getPendingWithInfo(): ({
type: "stream";
path: Incremental.Path;
delivered?: undefined;
} | {
type: "defer";
delivered: boolean;
path: Incremental.Path;
})[];
// (undocumented)
handle(cacheData: TData | DeepPartial<TData> | null | undefined, chunk: GraphQL17Alpha9Handler.Chunk<TData>): FormattedExecutionResult<TData>;
// (undocumented)
hasNext: boolean;
// (undocumented)
get pending(): GraphQL17Alpha9Handler.PendingResult[];
get streamInfo(): StreamInfoTrie_2 | undefined;
}

// @public (undocumented)
Expand Down Expand Up @@ -272,6 +306,33 @@ export class NotImplementedHandler implements Incremental.Handler<never> {
startRequest: any;
}

// @public (undocumented)
class StreamArrayState {
constructor(path: Incremental_2.Path);
// (undocumented)
depend(): void;
// (undocumented)
get streamPosition(): number;
set streamPosition(value: number);
// (undocumented)
truncate: boolean;
}

// @internal @deprecated (undocumented)
type StreamInfoTrie = Trie<{
current: Incremental_2.StreamFieldInfo;
state: StreamArrayState;
previous?: {
incoming: unknown;
streamFieldInfo: Incremental_2.StreamFieldInfo;
result: unknown;
};
}>;

// Warnings were encountered during analysis:
//
// src/utilities/internal/types/StreamInfoTrie.ts:10:3 - (ae-forgotten-export) The symbol "StreamArrayState" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

```
31 changes: 27 additions & 4 deletions .api-reports/api-report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1598,17 +1598,33 @@ namespace Incremental {
}
// (undocumented)
interface IncrementalRequest<Chunk extends Record<string, unknown>, TData> {
// Warning: (ae-forgotten-export) The symbol "Incremental" needs to be exported by the entry point index.d.ts
//
// (undocumented)
getPendingType?: (id: string) => "defer" | "stream";
getPendingWithInfo?: () => Array<PendingItemWithInfo>;
// (undocumented)
handle: (cacheData: TData | DeepPartial<TData> | undefined | null, chunk: Chunk) => FormattedExecutionResult<TData>;
// (undocumented)
hasNext: boolean;
// (undocumented)
pending?: Array<Incremental.PendingResult>;
readonly streamInfo?: StreamInfoTrie;
}
// (undocumented)
type Path = ReadonlyArray<string | number>;
// @internal @deprecated (undocumented)
interface PendingDeferResultWithInfo {
// (undocumented)
delivered: boolean;
// (undocumented)
path: Incremental.Path;
// (undocumented)
type: "defer";
}
// Warning: (ae-forgotten-export) The symbol "Incremental" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Incremental" needs to be exported by the entry point index.d.ts
//
// (undocumented)
type PendingItemWithInfo = PendingDeferResultWithInfo | PendingStreamResultWithInfo;
// (undocumented)
interface PendingResult {
// (undocumented)
Expand All @@ -1619,6 +1635,13 @@ namespace Incremental {
path: Incremental.Path;
}
// @internal @deprecated (undocumented)
interface PendingStreamResultWithInfo {
// (undocumented)
path: Incremental.Path;
// (undocumented)
type: "stream";
}
// @internal @deprecated (undocumented)
interface StartRequestOptions {
// (undocumented)
query: DocumentNode;
Expand Down Expand Up @@ -3263,8 +3286,8 @@ interface WriteContext extends ReadMergeModifyContext {
// src/cache/inmemory/types.ts:162:3 - (ae-forgotten-export) The symbol "FragmentRegistryAPI" needs to be exported by the entry point index.d.ts
// src/core/ApolloClient.ts:201:5 - (ae-forgotten-export) The symbol "IgnoreModifier" needs to be exported by the entry point index.d.ts
// src/core/ApolloClient.ts:635:5 - (ae-forgotten-export) The symbol "NextFetchPolicyContext" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:375:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
// src/core/QueryManager.ts:195:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts
// src/core/ObservableQuery.ts:376:5 - (ae-forgotten-export) The symbol "QueryManager" needs to be exported by the entry point index.d.ts
// src/core/QueryManager.ts:196:5 - (ae-forgotten-export) The symbol "MutationStoreValue" needs to be exported by the entry point index.d.ts
// src/local-state/LocalState.ts:149:5 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts
// src/local-state/LocalState.ts:202:7 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts
// src/local-state/LocalState.ts:245:7 - (ae-forgotten-export) The symbol "LocalState" needs to be exported by the entry point index.d.ts
Expand Down
5 changes: 5 additions & 0 deletions .changeset/chilly-actors-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fix an issue where a `network-only` query leaked partial cache data for `@defer` fragments that were not delivered by the network due to an error that bubbled to the `@defer` fragment boundary.
5 changes: 5 additions & 0 deletions .changeset/loud-bulldogs-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fix an issue where a `@defer` query reported the `dataState` as `complete` instead of `streaming` when an error occurs on a deferred field that bubbled to the defer boundary.
5 changes: 5 additions & 0 deletions .changeset/swift-starfishes-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fix an invariant error thrown when a `@defer` boundary received a payload after it had already been marked complete.
51 changes: 51 additions & 0 deletions src/cache/inmemory/__tests__/cache.diff/incremental.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9645,6 +9645,57 @@ test("strips both a partial pending @defer boundary and a complete pending sibli
});
});

test("keeps fields shared with a delivered @defer boundary while pruning a pending boundary at a nested path", () => {
const cache = new InMemoryCache();
const query = gql`
query {
... @defer {
hero {
name
}
}
hero {
id
... @defer {
name
homePlanet
}
}
}
`;

cache.writeQuery({
query,
data: {
hero: {
__typename: "Hero",
id: "1",
name: "Luke",
homePlanet: "Tatooine",
},
},
});

const deferInfo: DeferInfoTrie = new Trie();
deferInfo.lookup("hero");

expect(
cache.diff({
query,
optimistic: true,
returnPartialData: false,
[handleIncrementalSymbol]: { deferInfo },
})
).toStrictEqualTyped({
result: markAsStreaming({
hero: { __typename: "Hero", id: "1", name: "Luke" },
}),
dataState: "streaming",
complete: false,
missing: undefined,
});
});

function getMissingMessage(fieldName: string, obj: Record<string, unknown>) {
return `Can't find field '${fieldName}' on ${
isReference(obj) ?
Expand Down
4 changes: 3 additions & 1 deletion src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
import { invariant } from "@apollo/client/utilities/invariant";

import type { ApolloClient } from "./ApolloClient.js";
import { dataStateErrorCache } from "./dataStateErrorCache.js";
import { NetworkStatus } from "./networkStatus.js";
import type { QueryManager } from "./QueryManager.js";
import type {
Expand Down Expand Up @@ -2018,7 +2019,8 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
: notification.value;

if (notification.kind === "E" && result.dataState === "streaming") {
result.dataState = "complete" as any;
result.dataState =
dataStateErrorCache.get(notification.error) ?? ("complete" as any);
}

if (result.error) {
Expand Down
Loading
Loading