Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

fix(graphcache): Cascade `@defer`, `@_optional`, and `@_required` state only per nested fragment spread (#3517)

Changed files
+43 -18
.changeset
exchanges
graphcache
+5
.changeset/sixty-needles-exercise.md
···
+
---
+
'@urql/exchange-graphcache': patch
+
---
+
+
Prevent `@defer` from being applied in child field selections. Previously, a child field (i.e. a nested field) under a `@defer`-ed fragment would also become optional, which was based on a prior version of the DeferStream spec which didn't require deferred fields to be delivered as a group.
+4 -2
exchanges/graphcache/src/cacheExchange.test.ts
···
todos {
id
text
-
... on Todo @_optional {
-
completed
+
... @_optional {
+
... on Todo {
+
completed
+
}
}
}
}
+2 -2
exchanges/graphcache/src/operations/query.ts
···
const iterate = makeSelectionIterator(
entityKey,
entityKey,
-
deferRef,
+
false,
undefined,
select,
ctx
···
const iterate = makeSelectionIterator(
typename,
entityKey,
-
deferRef,
+
false,
undefined,
select,
ctx
+31 -13
exchanges/graphcache/src/operations/shared.ts
···
entityKey: string,
error: CombinedError | undefined
): Context => {
-
deferRef = false;
-
const ctx: Context = {
store,
variables,
···
(): FormattedNode<FieldNode> | undefined;
}
-
export const makeSelectionIterator = (
-
typename: void | string,
+
// NOTE: Outside of this file, we expect `_defer` to always be reset to `false`
+
export function makeSelectionIterator(
+
typename: undefined | string,
+
entityKey: string,
+
_defer: false,
+
_optional: undefined,
+
selectionSet: FormattedNode<SelectionSet>,
+
ctx: Context
+
): SelectionIterator;
+
// NOTE: Inside this file we expect the state to be recursively passed on
+
export function makeSelectionIterator(
+
typename: undefined | string,
entityKey: string,
-
defer: boolean,
-
optional: boolean | undefined,
+
_defer: boolean,
+
_optional: undefined | boolean,
selectionSet: FormattedNode<SelectionSet>,
ctx: Context
-
): SelectionIterator => {
+
): SelectionIterator;
+
+
export function makeSelectionIterator(
+
typename: undefined | string,
+
entityKey: string,
+
_defer: boolean,
+
_optional: boolean | undefined,
+
selectionSet: FormattedNode<SelectionSet>,
+
ctx: Context
+
): SelectionIterator {
let child: SelectionIterator | void;
let index = 0;
···
let node: FormattedNode<FieldNode> | undefined;
while (child || index < selectionSet.length) {
node = undefined;
-
deferRef = defer;
-
optionalRef = optional;
+
deferRef = _defer;
+
optionalRef = _optional;
if (child) {
if ((node = child())) {
return node;
···
if (isMatching) {
if (process.env.NODE_ENV !== 'production')
pushDebugNode(typename, fragment);
-
const isFragmentOptional = isOptional(select);
child = makeSelectionIterator(
typename,
entityKey,
-
defer || isDeferred(select, ctx.variables),
-
isFragmentOptional,
+
_defer || isDeferred(select, ctx.variables),
+
isFragmentOptional !== undefined
+
? isFragmentOptional
+
: _optional,
getSelectionSet(fragment),
ctx
);
···
}
}
};
-
};
+
}
export const ensureData = (x: DataField): Data | NullArray<Data> | null =>
x == null ? null : (x as Data | NullArray<Data>);
+1 -1
exchanges/graphcache/src/operations/write.ts
···
const iterate = makeSelectionIterator(
typename,
entityKey || typename,
-
deferRef,
+
false,
undefined,
select,
ctx