Skip to content

Commit a07ca68

Browse files
committed
Allow missing fields from defer payloads
1 parent 3cfded6 commit a07ca68

12 files changed

+398
-8
lines changed

packages/relay-runtime/multi-actor-environment/MultiActorEnvironment.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type MultiActorEnvironmentConfig = $ReadOnly<{
7171
scheduler?: ?TaskScheduler,
7272
shouldProcessClientComponents?: ?boolean,
7373
treatMissingFieldsAsNull?: boolean,
74+
deferDeduplicatedFields?: boolean,
7475
}>;
7576

7677
class MultiActorEnvironment implements IMultiActorEnvironment {
@@ -91,6 +92,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
9192
+_scheduler: ?TaskScheduler;
9293
+_shouldProcessClientComponents: ?boolean;
9394
+_treatMissingFieldsAsNull: boolean;
95+
+_deferDeduplicatedFields: boolean;
9496

9597
constructor(config: MultiActorEnvironmentConfig) {
9698
this._actorEnvironments = new Map();
@@ -106,6 +108,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
106108
this._relayFieldLogger = config.relayFieldLogger ?? defaultRelayFieldLogger;
107109
this._shouldProcessClientComponents = config.shouldProcessClientComponents;
108110
this._treatMissingFieldsAsNull = config.treatMissingFieldsAsNull ?? false;
111+
this._deferDeduplicatedFields = config.deferDeduplicatedFields ?? false;
109112
this._isServer = config.isServer ?? false;
110113
this._missingFieldHandlers = config.missingFieldHandlers ?? [];
111114
this._createStoreForActor = config.createStoreForActor;
@@ -471,6 +474,7 @@ class MultiActorEnvironment implements IMultiActorEnvironment {
471474
return this.forActor(actorIdentifier).getStore();
472475
},
473476
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
477+
deferDeduplicatedFields: this._deferDeduplicatedFields,
474478
updater,
475479
log: this._logFn,
476480
normalizeResponse: this._normalizeResponse,

packages/relay-runtime/store/OperationExecutor.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export type ExecuteConfig<TMutation: MutationParameters> = {
8888
+sink: Sink<GraphQLResponse>,
8989
+source: RelayObservable<GraphQLResponse>,
9090
+treatMissingFieldsAsNull: boolean,
91+
+deferDeduplicatedFields: boolean,
9192
+updater?: ?SelectorStoreUpdater<TMutation['response']>,
9293
+log: LogFunction,
9394
};
@@ -127,6 +128,7 @@ class Executor<TMutation: MutationParameters> {
127128
_actorIdentifier: ActorIdentifier;
128129
_getDataID: GetDataID;
129130
_treatMissingFieldsAsNull: boolean;
131+
_deferDeduplicatedFields: boolean;
130132
_incrementalPayloadsPending: boolean;
131133
_incrementalResults: Map<Label, Map<PathKey, IncrementalResults>>;
132134
_log: LogFunction;
@@ -177,13 +179,15 @@ class Executor<TMutation: MutationParameters> {
177179
sink,
178180
source,
179181
treatMissingFieldsAsNull,
182+
deferDeduplicatedFields,
180183
updater,
181184
log,
182185
normalizeResponse,
183186
}: ExecuteConfig<TMutation>): void {
184187
this._actorIdentifier = actorIdentifier;
185188
this._getDataID = getDataID;
186189
this._treatMissingFieldsAsNull = treatMissingFieldsAsNull;
190+
this._deferDeduplicatedFields = deferDeduplicatedFields;
187191
this._incrementalPayloadsPending = false;
188192
this._incrementalResults = new Map();
189193
this._log = log;
@@ -660,6 +664,7 @@ class Executor<TMutation: MutationParameters> {
660664
path: [],
661665
shouldProcessClientComponents: this._shouldProcessClientComponents,
662666
treatMissingFieldsAsNull,
667+
deferDeduplicatedFields: false,
663668
},
664669
this._useExecTimeResolvers,
665670
);
@@ -778,6 +783,7 @@ class Executor<TMutation: MutationParameters> {
778783
path: followupPayload.path,
779784
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
780785
shouldProcessClientComponents: this._shouldProcessClientComponents,
786+
deferDeduplicatedFields: false,
781787
},
782788
this._useExecTimeResolvers,
783789
);
@@ -862,6 +868,7 @@ class Executor<TMutation: MutationParameters> {
862868
path: [],
863869
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
864870
shouldProcessClientComponents: this._shouldProcessClientComponents,
871+
deferDeduplicatedFields: false,
865872
},
866873
this._useExecTimeResolvers,
867874
);
@@ -1327,6 +1334,7 @@ class Executor<TMutation: MutationParameters> {
13271334
log: this._log,
13281335
path: placeholder.path,
13291336
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1337+
deferDeduplicatedFields: this._deferDeduplicatedFields,
13301338
shouldProcessClientComponents: this._shouldProcessClientComponents,
13311339
},
13321340
this._useExecTimeResolvers,
@@ -1560,6 +1568,7 @@ class Executor<TMutation: MutationParameters> {
15601568
log: this._log,
15611569
path: [...normalizationPath, responseKey, String(itemIndex)],
15621570
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
1571+
deferDeduplicatedFields: false,
15631572
shouldProcessClientComponents: this._shouldProcessClientComponents,
15641573
},
15651574
this._useExecTimeResolvers,

packages/relay-runtime/store/RelayModernEnvironment.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export type EnvironmentConfig = {
6767
+configName?: string,
6868
+handlerProvider?: ?HandlerProvider,
6969
+treatMissingFieldsAsNull?: boolean,
70+
+deferDeduplicatedFields?: boolean,
7071
+log?: ?LogFunction,
7172
+operationLoader?: ?OperationLoader,
7273
+network: INetwork,
@@ -97,6 +98,7 @@ class RelayModernEnvironment implements IEnvironment {
9798
_operationTracker: OperationTracker;
9899
_getDataID: GetDataID;
99100
_treatMissingFieldsAsNull: boolean;
101+
_deferDeduplicatedFields: boolean;
100102
_operationExecutions: Map<string, ActiveState>;
101103
+options: mixed;
102104
+_isServer: boolean;
@@ -106,6 +108,7 @@ class RelayModernEnvironment implements IEnvironment {
106108
constructor(config: EnvironmentConfig) {
107109
this.configName = config.configName;
108110
this._treatMissingFieldsAsNull = config.treatMissingFieldsAsNull === true;
111+
this._deferDeduplicatedFields = config.deferDeduplicatedFields === true;
109112
const operationLoader = config.operationLoader;
110113
if (__DEV__) {
111114
if (operationLoader != null) {
@@ -500,6 +503,7 @@ class RelayModernEnvironment implements IEnvironment {
500503
return store;
501504
},
502505
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
506+
deferDeduplicatedFields: this._deferDeduplicatedFields,
503507
updater,
504508
normalizeResponse: this._normalizeResponse,
505509
});

packages/relay-runtime/store/RelayModernStore.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class RelayModernStore implements Store {
115115
_resolverContext: ?ResolverContext;
116116
_actorIdentifier: ?ActorIdentifier;
117117
_treatMissingFieldsAsNull: boolean;
118+
_deferDeduplicatedFields: boolean;
118119

119120
constructor(
120121
source: MutableRecordSource,
@@ -134,6 +135,7 @@ class RelayModernStore implements Store {
134135
// These additional config options are only used if the experimental
135136
// @outputType resolver feature is used
136137
treatMissingFieldsAsNull?: ?boolean,
138+
deferDeduplicatedFields?: ?boolean,
137139
actorIdentifier?: ?ActorIdentifier,
138140
},
139141
) {
@@ -182,6 +184,7 @@ class RelayModernStore implements Store {
182184
options?.shouldProcessClientComponents ?? false;
183185

184186
this._treatMissingFieldsAsNull = options?.treatMissingFieldsAsNull ?? false;
187+
this._deferDeduplicatedFields = options?.deferDeduplicatedFields ?? false;
185188
this._actorIdentifier = options?.actorIdentifier;
186189

187190
initializeRecordSource(this._recordSource);
@@ -827,6 +830,7 @@ class RelayModernStore implements Store {
827830
getDataID: this._getDataID,
828831
log: this.__log,
829832
treatMissingFieldsAsNull: this._treatMissingFieldsAsNull,
833+
deferDeduplicatedFields: this._deferDeduplicatedFields,
830834
shouldProcessClientComponents: this._shouldProcessClientComponents,
831835
actorIdentifier: this._actorIdentifier,
832836
};

packages/relay-runtime/store/RelayResponseNormalizer.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export type GetDataID = (
7676
export type NormalizationOptions = {
7777
+getDataID: GetDataID,
7878
+treatMissingFieldsAsNull: boolean,
79+
+deferDeduplicatedFields: boolean,
7980
+log: ?LogFunction,
8081
+path?: $ReadOnlyArray<string>,
8182
+shouldProcessClientComponents?: ?boolean,
@@ -114,6 +115,7 @@ class RelayResponseNormalizer {
114115
_getDataId: GetDataID;
115116
_handleFieldPayloads: Array<HandleFieldPayload>;
116117
_treatMissingFieldsAsNull: boolean;
118+
_deferDeduplicatedFields: boolean;
117119
_incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
118120
_isClientExtension: boolean;
119121
_isUnmatchedAbstractType: boolean;
@@ -136,6 +138,7 @@ class RelayResponseNormalizer {
136138
this._getDataId = options.getDataID;
137139
this._handleFieldPayloads = [];
138140
this._treatMissingFieldsAsNull = options.treatMissingFieldsAsNull;
141+
this._deferDeduplicatedFields = options.deferDeduplicatedFields;
139142
this._incrementalPlaceholders = [];
140143
this._isClientExtension = false;
141144
this._isUnmatchedAbstractType = false;
@@ -525,7 +528,9 @@ class RelayResponseNormalizer {
525528
// client is assumed to be correctly configured with
526529
// treatMissingFieldsAsNull=true.
527530
const isOptionalField =
528-
this._isClientExtension || this._isUnmatchedAbstractType;
531+
this._isClientExtension ||
532+
this._isUnmatchedAbstractType ||
533+
this._deferDeduplicatedFields;
529534

530535
if (isOptionalField) {
531536
// Field not expected to exist regardless of whether the server is pruning null

packages/relay-runtime/store/__tests__/RelayExperimentalGraphResponseHandler-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const {getRequest} = require('relay-runtime/query/GraphQLTag');
3030
const defaultOptions = {
3131
getDataID: defaultGetDataID,
3232
treatMissingFieldsAsNull: false,
33+
deferDeduplicatedFields: false,
3334
log: null,
3435
};
3536

packages/relay-runtime/store/__tests__/RelayExperimentalGraphResponseTransform-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const {ROOT_ID} = require('relay-runtime/store/RelayStoreUtils');
3232
const defaultOptions = {
3333
getDataID: defaultGetDataID,
3434
treatMissingFieldsAsNull: false,
35+
deferDeduplicatedFields: false,
3536
log: null,
3637
};
3738

packages/relay-runtime/store/__tests__/RelayModernEnvironment-ExecuteWithDefer-test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,83 @@ describe.each(['RelayModernEnvironment', 'MultiActorEnvironment'])(
312312
});
313313
});
314314

315+
it('processes deferred payloads with deduplicated fields', () => {
316+
environment = new RelayModernEnvironment({
317+
network: RelayNetwork.create(fetch),
318+
store,
319+
handlerProvider,
320+
deferDeduplicatedFields: true,
321+
});
322+
323+
query = graphql`
324+
query RelayModernEnvironmentExecuteWithDeferTestUserOverlappingFieldsQuery(
325+
$id: ID!
326+
) {
327+
node(id: $id) {
328+
... on User {
329+
id
330+
name
331+
...RelayModernEnvironmentExecuteWithDeferTestUserOverlappingFieldsFragment
332+
@dangerously_unaliased_fixme
333+
@defer(label: "UserFragment")
334+
}
335+
}
336+
}
337+
`;
338+
fragment = graphql`
339+
fragment RelayModernEnvironmentExecuteWithDeferTestUserOverlappingFieldsFragment on User {
340+
name
341+
alternate_name
342+
}
343+
`;
344+
345+
variables = {id: '1'};
346+
operation = createOperationDescriptor(query, variables);
347+
selector = createReaderSelector(fragment, '1', {}, operation.request);
348+
349+
const initialSnapshot = environment.lookup(selector);
350+
const callback = jest.fn<[Snapshot], void>();
351+
environment.subscribe(initialSnapshot, callback);
352+
353+
environment.execute({operation}).subscribe(callbacks);
354+
dataSource.next({
355+
data: {
356+
node: {
357+
__typename: 'User',
358+
id: '1',
359+
name: 'joe',
360+
},
361+
},
362+
});
363+
364+
jest.runAllTimers();
365+
next.mockClear();
366+
callback.mockClear();
367+
368+
dataSource.next({
369+
data: {
370+
alternate_name: 'joe2',
371+
},
372+
label:
373+
'RelayModernEnvironmentExecuteWithDeferTestUserOverlappingFieldsQuery$defer$UserFragment',
374+
path: ['node'],
375+
extensions: {
376+
is_final: true,
377+
},
378+
});
379+
380+
expect(complete).toBeCalledTimes(0);
381+
expect(error).toBeCalledTimes(0);
382+
expect(next).toBeCalledTimes(1);
383+
expect(callback).toBeCalledTimes(1);
384+
const snapshot = callback.mock.calls[0][0];
385+
expect(snapshot.isMissingData).toBe(false);
386+
expect(snapshot.data).toEqual({
387+
name: 'joe',
388+
alternate_name: 'joe2',
389+
});
390+
});
391+
315392
describe('Query with exec time resolvers', () => {
316393
let resolverOperation;
317394
beforeEach(() => {

0 commit comments

Comments
 (0)