/** * 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 };