Files
30-seconds-of-code/node_modules/relay-runtime/lib/RelayResponseNormalizer.js
2019-08-20 15:52:05 +02:00

376 lines
18 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
var _toConsumableArray2 = require("@babel/runtime/helpers/interopRequireDefault")(require("@babel/runtime/helpers/toConsumableArray"));
/**
* Normalizes the results of a query and standard GraphQL response, writing the
* normalized records/fields into the given MutableRecordSource.
*
* If handleStrippedNulls is true, will replace fields on the Selector that
* are not present in the response with null. Otherwise will leave fields unset.
*/
function normalize(recordSource, selector, response) {
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {
handleStrippedNulls: false
};
var dataID = selector.dataID,
node = selector.node,
variables = selector.variables;
var normalizer = new RelayResponseNormalizer(recordSource, variables, options);
return normalizer.normalizeResponse(node, dataID, response);
}
/**
* @private
*
* Helper for handling payloads.
*/
var RelayResponseNormalizer =
/*#__PURE__*/
function () {
function RelayResponseNormalizer(recordSource, variables, options) {
this._handleFieldPayloads = [];
this._handleStrippedNulls = options.handleStrippedNulls;
this._incrementalPayloads = [];
this._matchFieldPayloads = [];
this._path = options.path ? (0, _toConsumableArray2["default"])(options.path) : [];
this._recordSource = recordSource;
this._variables = variables;
}
var _proto = RelayResponseNormalizer.prototype;
_proto.normalizeResponse = function normalizeResponse(node, dataID, data) {
var record = this._recordSource.get(dataID);
!record ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Expected root record `%s` to exist.', dataID) : require("fbjs/lib/invariant")(false) : void 0;
this._traverseSelections(node, record, data);
return {
incrementalPayloads: this._incrementalPayloads,
fieldPayloads: this._handleFieldPayloads,
matchPayloads: this._matchFieldPayloads
};
};
_proto._getVariableValue = function _getVariableValue(name) {
!this._variables.hasOwnProperty(name) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Undefined variable `%s`.', name) : require("fbjs/lib/invariant")(false) : void 0;
return this._variables[name];
};
_proto._getRecordType = function _getRecordType(data) {
var typeName = data[require("./RelayStoreUtils").TYPENAME_KEY];
!(typeName != null) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Expected a typename for record `%s`.', JSON.stringify(data, null, 2)) : require("fbjs/lib/invariant")(false) : void 0;
return typeName;
};
_proto._traverseSelections = function _traverseSelections(node, record, data) {
var _this = this;
node.selections.forEach(function (selection) {
if (selection.kind === require("./RelayConcreteNode").SCALAR_FIELD || selection.kind === require("./RelayConcreteNode").LINKED_FIELD) {
_this._normalizeField(node, selection, record, data);
} else if (selection.kind === require("./RelayConcreteNode").CONDITION) {
var conditionValue = _this._getVariableValue(selection.condition);
if (conditionValue === selection.passingValue) {
_this._traverseSelections(selection, record, data);
}
} else if (selection.kind === require("./RelayConcreteNode").INLINE_FRAGMENT) {
var typeName = require("./RelayModernRecord").getType(record);
if (typeName === selection.type) {
_this._traverseSelections(selection, record, data);
}
} else if (selection.kind === require("./RelayConcreteNode").LINKED_HANDLE || selection.kind === require("./RelayConcreteNode").SCALAR_HANDLE) {
var args = selection.args ? require("./RelayStoreUtils").getArgumentValues(selection.args, _this._variables) : {};
var fieldKey = require("./RelayStoreUtils").getStorageKey(selection, _this._variables);
var handleKey = require("./RelayStoreUtils").getHandleStorageKey(selection, _this._variables);
_this._handleFieldPayloads.push({
args: args,
dataID: require("./RelayModernRecord").getDataID(record),
fieldKey: fieldKey,
handle: selection.handle,
handleKey: handleKey
});
} else if (selection.kind === require("./RelayConcreteNode").MATCH_FIELD) {
_this._normalizeMatchField(node, selection, record, data);
} else if (selection.kind === require("./RelayConcreteNode").DEFER) {
_this._normalizeDefer(selection, record, data);
} else if (selection.kind === require("./RelayConcreteNode").STREAM) {
_this._normalizeStream(selection, record, data);
} else if (selection.kind === require("./RelayConcreteNode").FRAGMENT || selection.kind === require("./RelayConcreteNode").FRAGMENT_SPREAD) {
!false ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Unexpected ast kind `%s`.', selection.kind) : require("fbjs/lib/invariant")(false) : void 0;
} else {
selection;
!false ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Unexpected ast kind `%s`.', selection.kind) : require("fbjs/lib/invariant")(false) : void 0;
}
});
};
_proto._normalizeDefer = function _normalizeDefer(defer, record, data) {
var isDeferred = defer["if"] === null || this._getVariableValue(defer["if"]);
if (process.env.NODE_ENV !== "production") {
process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(typeof isDeferred === 'boolean', 'RelayResponseNormalizer: Expected value for @defer `if` argument to ' + 'be a boolean, got `%s`.', isDeferred) : void 0;
}
if (isDeferred === false) {
// If defer is disabled there will be no additional response chunk:
// normalize the data already present.
this._traverseSelections(defer, record, data);
} else {
// Otherwise data *for this selection* should not be present: enqueue
// metadata to process the subsequent response chunk.
this._incrementalPayloads.push({
kind: 'defer',
label: defer.label,
path: (0, _toConsumableArray2["default"])(this._path),
selector: {
dataID: require("./RelayModernRecord").getDataID(record),
node: defer,
variables: this._variables
}
});
}
};
_proto._normalizeStream = function _normalizeStream(stream, record, data) {
// Always normalize regardless of whether streaming is enabled or not,
// this populates the initial array value (including any items when
// initial_count > 0).
this._traverseSelections(stream, record, data);
var isStreamed = stream["if"] === null || this._getVariableValue(stream["if"]);
if (process.env.NODE_ENV !== "production") {
process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(typeof isStreamed === 'boolean', 'RelayResponseNormalizer: Expected value for @stream `if` argument ' + 'to be a boolean, got `%s`.', isStreamed) : void 0;
}
if (isStreamed === true) {
// If streaming is enabled, *also* emit metadata to process any
// response chunks that may be delivered.
this._incrementalPayloads.push({
kind: 'stream',
label: stream.label,
path: (0, _toConsumableArray2["default"])(this._path),
selector: {
dataID: require("./RelayModernRecord").getDataID(record),
node: stream,
variables: this._variables
}
});
}
};
_proto._normalizeMatchField = function _normalizeMatchField(parent, field, record, data) {
!(typeof data === 'object' && data) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'writeField(): Expected data for field `%s` to be an object.', field.name) : require("fbjs/lib/invariant")(false) : void 0;
var responseKey = field.alias || field.name;
var storageKey = require("./RelayStoreUtils").getStorageKey(field, this._variables);
var fieldValue = data[responseKey];
if (fieldValue == null) {
if (fieldValue === undefined && !this._handleStrippedNulls) {
// If we're not stripping nulls, undefined fields are unset
return;
}
if (process.env.NODE_ENV !== "production") {
process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(parent.kind === require("./RelayConcreteNode").LINKED_FIELD && parent.concreteType == null ? true : Object.prototype.hasOwnProperty.call(data, responseKey), 'RelayResponseNormalizer(): Payload did not contain a value ' + 'for field `%s: %s`. Check that you are parsing with the same ' + 'query that was used to fetch the payload.', responseKey, storageKey) : void 0;
}
require("./RelayModernRecord").setValue(record, storageKey, null);
return;
}
!(typeof fieldValue === 'object' && fieldValue) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected data for field `%s` to be an object.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
var typeName = this._getRecordType(fieldValue);
var match = field.matchesByType[typeName];
if (match == null) {
require("./RelayModernRecord").setValue(record, storageKey, null);
return;
}
var nextID = fieldValue.id || // Reuse previously generated client IDs
require("./RelayModernRecord").getLinkedRecordID(record, storageKey) || require("./generateRelayClientID")(require("./RelayModernRecord").getDataID(record), storageKey);
!(typeof nextID === 'string') ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected id on field `%s` to be a string.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
require("./RelayModernRecord").setLinkedRecordID(record, storageKey, nextID);
var nextRecord = this._recordSource.get(nextID);
if (!nextRecord) {
nextRecord = require("./RelayModernRecord").create(nextID, typeName);
this._recordSource.set(nextID, nextRecord);
} else if (process.env.NODE_ENV !== "production") {
this._validateRecordType(nextRecord, field, fieldValue);
}
var operationReference = fieldValue[require("./RelayStoreUtils").MATCH_FRAGMENT_KEY];
if (operationReference != null) {
this._matchFieldPayloads.push({
data: fieldValue,
dataID: nextID,
operationReference: operationReference,
path: (0, _toConsumableArray2["default"])(this._path).concat([responseKey]),
typeName: typeName,
variables: this._variables
});
}
};
_proto._normalizeField = function _normalizeField(parent, selection, record, data) {
!(typeof data === 'object' && data) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'writeField(): Expected data for field `%s` to be an object.', selection.name) : require("fbjs/lib/invariant")(false) : void 0;
var responseKey = selection.alias || selection.name;
var storageKey = require("./RelayStoreUtils").getStorageKey(selection, this._variables);
var fieldValue = data[responseKey];
if (fieldValue == null) {
if (fieldValue === undefined && !this._handleStrippedNulls) {
// If we're not stripping nulls, undefined fields are unset
return;
}
if (process.env.NODE_ENV !== "production") {
process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(parent.kind === require("./RelayConcreteNode").LINKED_FIELD && parent.concreteType == null ? true : Object.prototype.hasOwnProperty.call(data, responseKey), 'RelayResponseNormalizer(): Payload did not contain a value ' + 'for field `%s: %s`. Check that you are parsing with the same ' + 'query that was used to fetch the payload.', responseKey, storageKey) : void 0;
}
require("./RelayModernRecord").setValue(record, storageKey, null);
return;
}
if (selection.kind === require("./RelayConcreteNode").SCALAR_FIELD) {
require("./RelayModernRecord").setValue(record, storageKey, fieldValue);
} else if (selection.kind === require("./RelayConcreteNode").LINKED_FIELD) {
this._path.push(responseKey);
if (selection.plural) {
this._normalizePluralLink(selection, record, storageKey, fieldValue);
} else {
this._normalizeLink(selection, record, storageKey, fieldValue);
}
this._path.pop();
} else if (selection.kind === require("./RelayConcreteNode").MATCH_FIELD) {
!false ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Unexpected ast kind `%s` during normalization.', selection.kind) : require("fbjs/lib/invariant")(false) : void 0;
} else {
selection;
!false ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer(): Unexpected ast kind `%s` during normalization.', selection.kind) : require("fbjs/lib/invariant")(false) : void 0;
}
};
_proto._normalizeLink = function _normalizeLink(field, record, storageKey, fieldValue) {
!(typeof fieldValue === 'object' && fieldValue) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected data for field `%s` to be an object.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
var nextID = fieldValue.id || // Reuse previously generated client IDs
require("./RelayModernRecord").getLinkedRecordID(record, storageKey) || require("./generateRelayClientID")(require("./RelayModernRecord").getDataID(record), storageKey);
!(typeof nextID === 'string') ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected id on field `%s` to be a string.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
require("./RelayModernRecord").setLinkedRecordID(record, storageKey, nextID);
var nextRecord = this._recordSource.get(nextID);
if (!nextRecord) {
var typeName = field.concreteType || this._getRecordType(fieldValue);
nextRecord = require("./RelayModernRecord").create(nextID, typeName);
this._recordSource.set(nextID, nextRecord);
} else if (process.env.NODE_ENV !== "production") {
this._validateRecordType(nextRecord, field, fieldValue);
}
this._traverseSelections(field, nextRecord, fieldValue);
};
_proto._normalizePluralLink = function _normalizePluralLink(field, record, storageKey, fieldValue) {
var _this2 = this;
!Array.isArray(fieldValue) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected data for field `%s` to be an array ' + 'of objects.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
var prevIDs = require("./RelayModernRecord").getLinkedRecordIDs(record, storageKey);
var nextIDs = [];
fieldValue.forEach(function (item, nextIndex) {
// validate response data
if (item == null) {
nextIDs.push(item);
return;
}
_this2._path.push(String(nextIndex));
!(typeof item === 'object') ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected elements for field `%s` to be ' + 'objects.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
var nextID = item.id || prevIDs && prevIDs[nextIndex] || // Reuse previously generated client IDs
require("./generateRelayClientID")(require("./RelayModernRecord").getDataID(record), storageKey, nextIndex);
!(typeof nextID === 'string') ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayResponseNormalizer: Expected id of elements of field `%s` to ' + 'be strings.', storageKey) : require("fbjs/lib/invariant")(false) : void 0;
nextIDs.push(nextID);
var nextRecord = _this2._recordSource.get(nextID);
if (!nextRecord) {
var typeName = field.concreteType || _this2._getRecordType(item);
nextRecord = require("./RelayModernRecord").create(nextID, typeName);
_this2._recordSource.set(nextID, nextRecord);
} else if (process.env.NODE_ENV !== "production") {
_this2._validateRecordType(nextRecord, field, item);
}
_this2._traverseSelections(field, nextRecord, item);
_this2._path.pop();
});
require("./RelayModernRecord").setLinkedRecordIDs(record, storageKey, nextIDs);
};
/**
* Warns if the type of the record does not match the type of the field/payload.
*/
_proto._validateRecordType = function _validateRecordType(record, field, payload) {
var typeName = field.kind === 'LinkedField' ? field.concreteType || this._getRecordType(payload) : this._getRecordType(payload);
process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(require("./RelayModernRecord").getType(record) === typeName, 'RelayResponseNormalizer: Invalid record `%s`. Expected %s to be ' + 'be consistent, but the record was assigned conflicting types `%s` ' + 'and `%s`. The GraphQL server likely violated the globally unique ' + 'id requirement by returning the same id for different objects.', require("./RelayModernRecord").getDataID(record), require("./RelayStoreUtils").TYPENAME_KEY, require("./RelayModernRecord").getType(record), typeName) : void 0;
};
return RelayResponseNormalizer;
}(); // eslint-disable-next-line no-func-assign
normalize = require("./RelayProfiler").instrument('RelayResponseNormalizer.normalize', normalize);
module.exports = {
normalize: normalize
};