376 lines
18 KiB
JavaScript
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
|
|
}; |