Skip to content

Commit

Permalink
Handle more node types
Browse files Browse the repository at this point in the history
Reviewed By: tyao1

Differential Revision: D32608677

fbshipit-source-id: 641c8aeb846102d139b4a8deb4525e1fe6ac8b86
  • Loading branch information
captbaritone authored and facebook-github-bot committed Dec 7, 2021
1 parent 03647ab commit 7730a5b
Show file tree
Hide file tree
Showing 8 changed files with 673 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
* @format
*/

import type {ActorIdentifier} from '../multi-actor-environment/ActorIdentifier';
import type {NormalizationOptions} from './RelayResponseNormalizer';
import type {IncrementalDataPlaceholder} from './RelayStoreTypes';
import type {
DataID,
NormalizationField,
Expand All @@ -22,15 +25,20 @@ import type {
} from 'relay-runtime/util/NormalizationNode';
import type {Variables} from 'relay-runtime/util/RelayRuntimeTypes';

const {getLocalVariables} = require('./RelayConcreteVariables');
const {createNormalizationSelector} = require('./RelayModernSelector');
const invariant = require('invariant');
const {generateClientID} = require('relay-runtime');
const defaultGetDataID = require('relay-runtime/store/defaultGetDataID');
const {
ROOT_TYPE,
TYPENAME_KEY,
getStorageKey,
} = require('relay-runtime/store/RelayStoreUtils');
const {
CLIENT_EXTENSION,
CONDITION,
DEFER,
FRAGMENT_SPREAD,
INLINE_FRAGMENT,
LINKED_FIELD,
SCALAR_FIELD,
Expand Down Expand Up @@ -118,24 +126,31 @@ export type GraphModeResponse = Iterable<GraphModeChunk>;
export function normalizeResponse(
response: PayloadData,
selector: NormalizationSelector,
options: NormalizationOptions,
): GraphModeResponse {
const {node, variables, dataID} = selector;
const normalizer = new GraphModeNormalizer(variables);
const normalizer = new GraphModeNormalizer(variables, options);
return normalizer.normalizeResponse(node, dataID, response);
}

class GraphModeNormalizer {
_cacheKeyToStreamID: Map<string, number>;
_sentFields: Map<string, Set<string>>;
_getDataId: GetDataID;
_nextStreamID: number;
_getDataID: GetDataID;
_variables: Variables;
constructor(variables: Variables) {
_path: Array<string>;
_incrementalPlaceholders: Array<IncrementalDataPlaceholder>;
_actorIdentifier: ?ActorIdentifier;
constructor(variables: Variables, options: NormalizationOptions) {
this._actorIdentifier = options.actorIdentifier;
this._path = options.path ? [...options.path] : [];
this._getDataID = options.getDataID;
this._cacheKeyToStreamID = new Map();
this._sentFields = new Map();
this._nextStreamID = 0;
this._variables = variables;
this._getDataID = defaultGetDataID;
}

_getStreamID() {
Expand Down Expand Up @@ -168,6 +183,15 @@ class GraphModeNormalizer {
return getStorageKey(selection, this._variables);
}

_getVariableValue(name: string): mixed {
invariant(
this._variables.hasOwnProperty(name),
'Unexpected undefined variable `%s`.',
name,
);
return this._variables[name];
}

*normalizeResponse(
node: NormalizationNode,
dataID: DataID,
Expand Down Expand Up @@ -232,6 +256,8 @@ class GraphModeNormalizer {

const storageKey = this._getStorageKey(selection);

this._path.push(responseKey);

const fieldValue = yield* this._traverseLinkedField(
selection.plural,
fieldData,
Expand All @@ -240,6 +266,8 @@ class GraphModeNormalizer {
parentID,
);

this._path.pop();

// TODO: We could also opt to confirm that this matches the previously
// seen value.
if (sentFields.has(storageKey)) {
Expand Down Expand Up @@ -285,6 +313,72 @@ class GraphModeNormalizer {
);
break;
}
case FRAGMENT_SPREAD: {
const prevVariables = this._variables;
this._variables = getLocalVariables(
this._variables,
selection.fragment.argumentDefinitions,
selection.args,
);
yield* this._traverseSelections(
selection.fragment,
data,
parentFields,
parentID,
sentFields,
);
this._variables = prevVariables;
break;
}
case CONDITION:
const conditionValue = Boolean(
this._getVariableValue(selection.condition),
);
if (conditionValue === selection.passingValue) {
yield* this._traverseSelections(
selection,
data,
parentFields,
parentID,
sentFields,
);
}
break;
case DEFER:
const isDeferred =
selection.if === null || this._getVariableValue(selection.if);
if (isDeferred === false) {
// If defer is disabled there will be no additional response chunk:
// normalize the data already present.
yield* this._traverseSelections(
selection,
data,
parentFields,
parentID,
sentFields,
);
} else {
// Otherwise data *for this selection* should not be present: enqueue
// metadata to process the subsequent response chunk.
this._incrementalPlaceholders.push({
kind: 'defer',
data,
label: selection.label,
path: [...this._path],
selector: createNormalizationSelector(
selection,
parentID,
this._variables,
),
typeName: this._getObjectType(data),
actorIdentifier: this._actorIdentifier,
});
}
break;
case CLIENT_EXTENSION:
// Since we are only expecting to handle server responses, we can skip
// over client extensions.
break;
default:
throw new Error(`Unexpected selection type: ${selection.kind}`);
}
Expand All @@ -311,6 +405,7 @@ class GraphModeNormalizer {

const fieldValue = [];
for (const [i, itemData] of fieldData.entries()) {
this._path.push(String(i));
const itemValue = yield* this._traverseLinkedField(
false,
itemData,
Expand All @@ -319,6 +414,7 @@ class GraphModeNormalizer {
parentID,
i,
);
this._path.pop();
fieldValue.push(itemValue);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import type {MutableRecordSource} from '../RelayStoreTypes';
import type {GraphQLTaggedNode, PayloadData, Variables} from 'relay-runtime';

const defaultGetDataID = require('../defaultGetDataID');
const {
handleGraphModeResponse,
} = require('../RelayExperimentalGraphResponseHandler');
Expand All @@ -26,6 +27,11 @@ const {ROOT_ID} = require('../RelayStoreUtils');
const {graphql} = require('relay-runtime');
const {getRequest} = require('relay-runtime/query/GraphQLTag');

const defaultOptions = {
getDataID: defaultGetDataID,
treatMissingFieldsAsNull: false,
};

function applyTransform(
query: GraphQLTaggedNode,
response: PayloadData,
Expand All @@ -36,7 +42,11 @@ function applyTransform(
ROOT_ID,
variables,
);
const graphModeResponse = normalizeResponse(response, selector);
const graphModeResponse = normalizeResponse(
response,
selector,
defaultOptions,
);

const recordSource = new RelayRecordSource();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {GraphModeChunk} from '../RelayExperimentalGraphResponseTransform';
import type {PayloadData, Variables} from 'relay-runtime';
import type {GraphQLTaggedNode} from 'relay-runtime/query/GraphQLTag';

const defaultGetDataID = require('../defaultGetDataID');
const {
normalizeResponse,
} = require('../RelayExperimentalGraphResponseTransform');
Expand All @@ -25,6 +26,11 @@ const {
} = require('relay-runtime/store/RelayModernSelector');
const {ROOT_ID} = require('relay-runtime/store/RelayStoreUtils');

const defaultOptions = {
getDataID: defaultGetDataID,
treatMissingFieldsAsNull: false,
};

function applyTransform(
query: GraphQLTaggedNode,
response: PayloadData,
Expand All @@ -35,7 +41,7 @@ function applyTransform(
ROOT_ID,
variables,
);
return Array.from(normalizeResponse(response, selector));
return Array.from(normalizeResponse(response, selector, defaultOptions));
}

test('Basic', () => {
Expand Down Expand Up @@ -562,3 +568,109 @@ test('Fragment Spread (gets inlined into `InlineFragment`)', () => {
]
`);
});

test('Fragment Spread @no_inline', () => {
graphql`
fragment RelayExperimentalGraphResponseTransformTest_no_inline_user_name on User
@no_inline {
name
}
`;
const query = graphql`
query RelayExperimentalGraphResponseTransformTestFragmentSpreadNoInlineQuery {
node(id: "10") {
...RelayExperimentalGraphResponseTransformTest_no_inline_user_name
}
}
`;
const response = {
node: {
__typename: 'User',
name: 'Elizabeth',
id: '10',
},
};

const actual = applyTransform(query, response, {});

expect(actual).toMatchInlineSnapshot(`
Array [
Object {
"$kind": "Record",
"$streamID": 0,
"__id": "10",
"__typename": "User",
"id": "10",
"name": "Elizabeth",
},
Object {
"$kind": "Record",
"$streamID": 1,
"__id": "client:root",
"__typename": "__Root",
"node(id:\\"10\\")": Object {
"__id": 0,
},
},
Object {
"$kind": "Complete",
},
]
`);
});

test('Traverses when @defer is disabled', () => {
graphql`
fragment RelayExperimentalGraphResponseTransformTest_condition on User {
name
}
`;
const query = graphql`
query RelayExperimentalGraphResponseTransformTestConditionQuery(
$id: ID!
$enableDefer: Boolean!
) {
node(id: $id) {
...RelayExperimentalGraphResponseTransformTest_condition
@defer(label: "TestFragment", if: $enableDefer)
}
}
`;
const response = {
node: {
__typename: 'User',
name: 'Elizabeth',
id: '10',
},
};

const actual = applyTransform(query, response, {
id: '1',
enableDefer: false,
});

expect(actual).toMatchInlineSnapshot(`
Array [
Object {
"$kind": "Record",
"$streamID": 0,
"__id": "10",
"__typename": "User",
"id": "10",
"name": "Elizabeth",
},
Object {
"$kind": "Record",
"$streamID": 1,
"__id": "client:root",
"__typename": "__Root",
"node(id:\\"1\\")": Object {
"__id": 0,
},
},
Object {
"$kind": "Complete",
},
]
`);
});
Loading

0 comments on commit 7730a5b

Please sign in to comment.