UNPKG

relay-runtime

Version:

A core runtime for building GraphQL-driven applications.

489 lines (488 loc) • 26.5 kB
'use strict'; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"]; var _createForOfIteratorHelper2 = _interopRequireDefault(require("@babel/runtime/helpers/createForOfIteratorHelper")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _require = require('../multi-actor-environment/ActorUtils'), ACTOR_IDENTIFIER_FIELD_NAME = _require.ACTOR_IDENTIFIER_FIELD_NAME, getActorIdentifierFromPayload = _require.getActorIdentifierFromPayload; var RelayFeatureFlags = require('../util/RelayFeatureFlags'); var _require2 = require('./ClientID'), generateClientID = _require2.generateClientID, isClientID = _require2.isClientID; var _require3 = require('./RelayConcreteVariables'), getLocalVariables = _require3.getLocalVariables; var _require4 = require('./RelayErrorTrie'), buildErrorTrie = _require4.buildErrorTrie, getErrorsByKey = _require4.getErrorsByKey, getNestedErrorTrieByKey = _require4.getNestedErrorTrieByKey; var RelayModernRecord = require('./RelayModernRecord'); var _require5 = require('./RelayModernSelector'), createNormalizationSelector = _require5.createNormalizationSelector; var _require6 = require('./RelayStoreUtils'), ROOT_ID = _require6.ROOT_ID, TYPENAME_KEY = _require6.TYPENAME_KEY, getArgumentValues = _require6.getArgumentValues, getHandleStorageKey = _require6.getHandleStorageKey, getModuleComponentKey = _require6.getModuleComponentKey, getModuleOperationKey = _require6.getModuleOperationKey, getStorageKey = _require6.getStorageKey; var _require7 = require('./TypeID'), TYPE_SCHEMA_TYPE = _require7.TYPE_SCHEMA_TYPE, generateTypeID = _require7.generateTypeID; var areEqual = require("fbjs/lib/areEqual"); var invariant = require('invariant'); var warning = require("fbjs/lib/warning"); function normalize(recordSource, selector, response, options, errors, useExecTimeResolvers) { var dataID = selector.dataID, node = selector.node, variables = selector.variables; var normalizer = new RelayResponseNormalizer(recordSource, variables, options, useExecTimeResolvers !== null && useExecTimeResolvers !== void 0 ? useExecTimeResolvers : false); return normalizer.normalizeResponse(node, dataID, response, errors); } var RelayResponseNormalizer = /*#__PURE__*/function () { function RelayResponseNormalizer(recordSource, variables, options, useExecTimeResolvers) { this._actorIdentifier = options.actorIdentifier; this._getDataId = options.getDataID; this._handleFieldPayloads = []; this._treatMissingFieldsAsNull = options.treatMissingFieldsAsNull; this._incrementalPlaceholders = []; this._isClientExtension = false; this._isUnmatchedAbstractType = false; this._useExecTimeResolvers = useExecTimeResolvers; this._followupPayloads = []; this._path = options.path ? (0, _toConsumableArray2["default"])(options.path) : []; this._recordSource = recordSource; this._variables = variables; this._shouldProcessClientComponents = options.shouldProcessClientComponents; this._log = options.log; } var _proto = RelayResponseNormalizer.prototype; _proto.normalizeResponse = function normalizeResponse(node, dataID, data, errors) { var record = this._recordSource.get(dataID); !record ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer(): Expected root record `%s` to exist.', dataID) : invariant(false) : void 0; this._assignClientAbstractTypes(node); this._errorTrie = buildErrorTrie(errors); this._traverseSelections(node, record, data); return { errors: errors, fieldPayloads: this._handleFieldPayloads, incrementalPlaceholders: this._incrementalPlaceholders, followupPayloads: this._followupPayloads, source: this._recordSource, isFinal: false }; }; _proto._assignClientAbstractTypes = function _assignClientAbstractTypes(node) { var clientAbstractTypes = node.clientAbstractTypes; if (clientAbstractTypes != null) { for (var _i = 0, _Object$keys = Object.keys(clientAbstractTypes); _i < _Object$keys.length; _i++) { var abstractType = _Object$keys[_i]; var _iterator = (0, _createForOfIteratorHelper2["default"])(clientAbstractTypes[abstractType]), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var concreteType = _step.value; var typeID = generateTypeID(concreteType); var typeRecord = this._recordSource.get(typeID); if (typeRecord == null) { typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE); this._recordSource.set(typeID, typeRecord); } RelayModernRecord.setValue(typeRecord, abstractType, true); } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } } } }; _proto._getVariableValue = function _getVariableValue(name) { !this._variables.hasOwnProperty(name) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer(): Undefined variable `%s`.', name) : invariant(false) : void 0; return this._variables[name]; }; _proto._getRecordType = function _getRecordType(data) { var typeName = data[TYPENAME_KEY]; !(typeName != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer(): Expected a typename for record `%s`.', JSON.stringify(data, null, 2)) : invariant(false) : void 0; return typeName; }; _proto._traverseSelections = function _traverseSelections(node, record, data) { for (var i = 0; i < node.selections.length; i++) { var selection = node.selections[i]; switch (selection.kind) { case 'ScalarField': case 'LinkedField': this._normalizeField(selection, record, data); break; case 'Condition': var conditionValue = Boolean(this._getVariableValue(selection.condition)); if (conditionValue === selection.passingValue) { this._traverseSelections(selection, record, data); } break; case 'FragmentSpread': { var prevVariables = this._variables; this._variables = getLocalVariables(this._variables, selection.fragment.argumentDefinitions, selection.args); this._traverseSelections(selection.fragment, record, data); this._variables = prevVariables; break; } case 'InlineFragment': { var abstractKey = selection.abstractKey; if (abstractKey == null) { var typeName = RelayModernRecord.getType(record); if (typeName === selection.type) { this._traverseSelections(selection, record, data); } } else { var implementsInterface = data.hasOwnProperty(abstractKey); var _typeName = RelayModernRecord.getType(record); var typeID = generateTypeID(_typeName); var typeRecord = this._recordSource.get(typeID); if (typeRecord == null) { typeRecord = RelayModernRecord.create(typeID, TYPE_SCHEMA_TYPE); this._recordSource.set(typeID, typeRecord); } RelayModernRecord.setValue(typeRecord, abstractKey, implementsInterface); if (implementsInterface) { this._traverseSelections(selection, record, data); } } break; } case 'TypeDiscriminator': { var _abstractKey = selection.abstractKey; var _implementsInterface = data.hasOwnProperty(_abstractKey); var _typeName2 = RelayModernRecord.getType(record); var _typeID = generateTypeID(_typeName2); var _typeRecord = this._recordSource.get(_typeID); if (_typeRecord == null) { _typeRecord = RelayModernRecord.create(_typeID, TYPE_SCHEMA_TYPE); this._recordSource.set(_typeID, _typeRecord); } RelayModernRecord.setValue(_typeRecord, _abstractKey, _implementsInterface); break; } case 'LinkedHandle': case 'ScalarHandle': var args = selection.args ? getArgumentValues(selection.args, this._variables) : {}; var fieldKey = getStorageKey(selection, this._variables); var handleKey = getHandleStorageKey(selection, this._variables); this._handleFieldPayloads.push({ args: args, dataID: RelayModernRecord.getDataID(record), fieldKey: fieldKey, handle: selection.handle, handleKey: handleKey, handleArgs: selection.handleArgs ? getArgumentValues(selection.handleArgs, this._variables) : {} }); break; case 'ModuleImport': this._normalizeModuleImport(selection, record, data); break; case 'Defer': this._normalizeDefer(selection, record, data); break; case 'Stream': this._normalizeStream(selection, record, data); break; case 'ClientExtension': var isClientExtension = this._isClientExtension; this._isClientExtension = true; this._traverseSelections(selection, record, data); this._isClientExtension = isClientExtension; break; case 'ClientComponent': if (this._shouldProcessClientComponents === false) { break; } this._traverseSelections(selection.fragment, record, data); break; case 'ActorChange': this._normalizeActorChange(selection, record, data); break; case 'RelayResolver': case 'RelayLiveResolver': if (!this._useExecTimeResolvers) { this._normalizeResolver(selection, record, data); } break; case 'ClientEdgeToClientObject': if (!this._useExecTimeResolvers) { this._normalizeResolver(selection.backingField, record, data); } break; default: selection; !false ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer(): Unexpected ast kind `%s`.', selection.kind) : invariant(false) : void 0; } } }; _proto._normalizeResolver = function _normalizeResolver(resolver, record, data) { if (resolver.fragment != null) { this._traverseSelections(resolver.fragment, record, data); } }; _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" ? warning(typeof isDeferred === 'boolean', 'RelayResponseNormalizer: Expected value for @defer `if` argument to ' + 'be a boolean, got `%s`.', isDeferred) : void 0; } if (isDeferred === false) { this._traverseSelections(defer, record, data); } else { this._incrementalPlaceholders.push({ kind: 'defer', data: data, label: defer.label, path: (0, _toConsumableArray2["default"])(this._path), selector: createNormalizationSelector(defer, RelayModernRecord.getDataID(record), this._variables), typeName: RelayModernRecord.getType(record), actorIdentifier: this._actorIdentifier }); } }; _proto._normalizeStream = function _normalizeStream(stream, record, data) { 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" ? warning(typeof isStreamed === 'boolean', 'RelayResponseNormalizer: Expected value for @stream `if` argument ' + 'to be a boolean, got `%s`.', isStreamed) : void 0; } if (isStreamed === true) { this._incrementalPlaceholders.push({ kind: 'stream', label: stream.label, path: (0, _toConsumableArray2["default"])(this._path), parentID: RelayModernRecord.getDataID(record), node: stream, variables: this._variables, actorIdentifier: this._actorIdentifier }); } }; _proto._normalizeModuleImport = function _normalizeModuleImport(moduleImport, record, data) { !(typeof data === 'object' && data) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected data for @module to be an object.') : invariant(false) : void 0; var typeName = RelayModernRecord.getType(record); var componentKey = getModuleComponentKey(moduleImport.documentName); var componentReference = moduleImport.componentModuleProvider || data[componentKey]; RelayModernRecord.setValue(record, componentKey, componentReference !== null && componentReference !== void 0 ? componentReference : null); var operationKey = getModuleOperationKey(moduleImport.documentName); var operationReference = moduleImport.operationModuleProvider || data[operationKey]; RelayModernRecord.setValue(record, operationKey, operationReference !== null && operationReference !== void 0 ? operationReference : null); if (operationReference != null) { this._followupPayloads.push({ kind: 'ModuleImportPayload', args: moduleImport.args, data: data, dataID: RelayModernRecord.getDataID(record), operationReference: operationReference, path: (0, _toConsumableArray2["default"])(this._path), typeName: typeName, variables: this._variables, actorIdentifier: this._actorIdentifier }); } }; _proto._normalizeField = function _normalizeField(selection, record, data) { !(typeof data === 'object' && data) ? process.env.NODE_ENV !== "production" ? invariant(false, 'writeField(): Expected data for field `%s` to be an object.', selection.name) : invariant(false) : void 0; var responseKey = selection.alias || selection.name; var storageKey = getStorageKey(selection, this._variables); var fieldValue = data[responseKey]; var isNoncompliantlyNullish = RelayFeatureFlags.ENABLE_NONCOMPLIANT_ERROR_HANDLING_ON_LISTS && Array.isArray(fieldValue) && fieldValue.length === 0; if (fieldValue == null || isNoncompliantlyNullish) { if (fieldValue === undefined) { var isOptionalField = this._isClientExtension || this._isUnmatchedAbstractType; if (isOptionalField) { return; } else if (!this._treatMissingFieldsAsNull) { if (process.env.NODE_ENV !== "production") { process.env.NODE_ENV !== "production" ? warning(false, '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; } return; } } if (selection.kind === 'ScalarField') { this._validateConflictingFieldsWithIdenticalId(record, storageKey, null); } if (isNoncompliantlyNullish) { if (selection.kind === 'LinkedField') { RelayModernRecord.setLinkedRecordIDs(record, storageKey, []); } else { RelayModernRecord.setValue(record, storageKey, []); } } else { RelayModernRecord.setValue(record, storageKey, null); } var errorTrie = this._errorTrie; if (errorTrie != null) { var errors = getErrorsByKey(errorTrie, responseKey); if (errors != null) { RelayModernRecord.setErrors(record, storageKey, errors); } } return; } if (selection.kind === 'ScalarField') { this._validateConflictingFieldsWithIdenticalId(record, storageKey, fieldValue); RelayModernRecord.setValue(record, storageKey, fieldValue); } else if (selection.kind === 'LinkedField') { this._path.push(responseKey); var oldErrorTrie = this._errorTrie; this._errorTrie = oldErrorTrie == null ? null : getNestedErrorTrieByKey(oldErrorTrie, responseKey); if (selection.plural) { this._normalizePluralLink(selection, record, storageKey, fieldValue); } else { this._normalizeLink(selection, record, storageKey, fieldValue); } this._errorTrie = oldErrorTrie; this._path.pop(); } else { selection; !false ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer(): Unexpected ast kind `%s` during normalization.', selection.kind) : invariant(false) : void 0; } }; _proto._normalizeActorChange = function _normalizeActorChange(selection, record, data) { var _field$concreteType; var field = selection.linkedField; !(typeof data === 'object' && data) ? process.env.NODE_ENV !== "production" ? invariant(false, '_normalizeActorChange(): Expected data for field `%s` to be an object.', field.name) : invariant(false) : void 0; var responseKey = field.alias || field.name; var storageKey = getStorageKey(field, this._variables); var fieldValue = data[responseKey]; if (fieldValue == null) { if (fieldValue === undefined) { var isOptionalField = this._isClientExtension || this._isUnmatchedAbstractType; if (isOptionalField) { return; } else if (!this._treatMissingFieldsAsNull) { if (process.env.NODE_ENV !== "production") { process.env.NODE_ENV !== "production" ? warning(false, '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; } return; } } RelayModernRecord.setValue(record, storageKey, null); return; } var actorIdentifier = getActorIdentifierFromPayload(fieldValue); if (actorIdentifier == null) { if (process.env.NODE_ENV !== "production") { process.env.NODE_ENV !== "production" ? warning(false, 'RelayResponseNormalizer: Payload did not contain a value ' + 'for field `%s`. Check that you are parsing with the same ' + 'query that was used to fetch the payload. Payload is `%s`.', ACTOR_IDENTIFIER_FIELD_NAME, JSON.stringify(fieldValue, null, 2)) : void 0; } RelayModernRecord.setValue(record, storageKey, null); return; } var typeName = (_field$concreteType = field.concreteType) !== null && _field$concreteType !== void 0 ? _field$concreteType : this._getRecordType(fieldValue); var nextID = this._getDataId(fieldValue, typeName) || RelayModernRecord.getLinkedRecordID(record, storageKey) || generateClientID(RelayModernRecord.getDataID(record), storageKey); !(typeof nextID === 'string') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected id on field `%s` to be a string.', storageKey) : invariant(false) : void 0; RelayModernRecord.setActorLinkedRecordID(record, storageKey, actorIdentifier, nextID); this._followupPayloads.push({ kind: 'ActorPayload', data: fieldValue, dataID: nextID, path: [].concat((0, _toConsumableArray2["default"])(this._path), [responseKey]), typeName: typeName, variables: this._variables, node: field, actorIdentifier: actorIdentifier }); }; _proto._normalizeLink = function _normalizeLink(field, record, storageKey, fieldValue) { var _field$concreteType2; !(typeof fieldValue === 'object' && fieldValue) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected data for field `%s` to be an object.', storageKey) : invariant(false) : void 0; var nextID = this._getDataId(fieldValue, (_field$concreteType2 = field.concreteType) !== null && _field$concreteType2 !== void 0 ? _field$concreteType2 : this._getRecordType(fieldValue)) || RelayModernRecord.getLinkedRecordID(record, storageKey) || generateClientID(RelayModernRecord.getDataID(record), storageKey); !(typeof nextID === 'string') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected id on field `%s` to be a string.', storageKey) : invariant(false) : void 0; this._validateConflictingLinkedFieldsWithIdenticalId(RelayModernRecord.getLinkedRecordID(record, storageKey), nextID, storageKey); RelayModernRecord.setLinkedRecordID(record, storageKey, nextID); var nextRecord = this._recordSource.get(nextID); if (!nextRecord) { var typeName = field.concreteType || this._getRecordType(fieldValue); nextRecord = RelayModernRecord.create(nextID, typeName); this._recordSource.set(nextID, nextRecord); } else { this._validateRecordType(nextRecord, field, fieldValue); } this._traverseSelections(field, nextRecord, fieldValue); }; _proto._normalizePluralLink = function _normalizePluralLink(field, record, storageKey, fieldValue) { var _this = this; !Array.isArray(fieldValue) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected data for field `%s` to be an array ' + 'of objects.', storageKey) : invariant(false) : void 0; var prevIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey); var nextIDs = []; fieldValue.forEach(function (item, nextIndex) { var _field$concreteType3; if (item == null) { nextIDs.push(item); return; } _this._path.push(String(nextIndex)); var oldErrorTrie = _this._errorTrie; _this._errorTrie = oldErrorTrie == null ? null : getNestedErrorTrieByKey(oldErrorTrie, nextIndex); !(typeof item === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected elements for field `%s` to be ' + 'objects.', storageKey) : invariant(false) : void 0; var nextID = _this._getDataId(item, (_field$concreteType3 = field.concreteType) !== null && _field$concreteType3 !== void 0 ? _field$concreteType3 : _this._getRecordType(item)) || prevIDs && prevIDs[nextIndex] || generateClientID(RelayModernRecord.getDataID(record), storageKey, nextIndex); !(typeof nextID === 'string') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayResponseNormalizer: Expected id of elements of field `%s` to ' + 'be strings.', storageKey) : invariant(false) : void 0; nextIDs.push(nextID); var nextRecord = _this._recordSource.get(nextID); if (!nextRecord) { var typeName = field.concreteType || _this._getRecordType(item); nextRecord = RelayModernRecord.create(nextID, typeName); _this._recordSource.set(nextID, nextRecord); } else { _this._validateRecordType(nextRecord, field, item); } if (prevIDs) { _this._validateConflictingLinkedFieldsWithIdenticalId(prevIDs[nextIndex], nextID, storageKey); } _this._traverseSelections(field, nextRecord, item); _this._errorTrie = oldErrorTrie; _this._path.pop(); }); RelayModernRecord.setLinkedRecordIDs(record, storageKey, nextIDs); }; _proto._validateRecordType = function _validateRecordType(record, field, payload) { if (RelayFeatureFlags.ENABLE_STORE_ID_COLLISION_LOGGING) { var _field$concreteType4; var typeName = (_field$concreteType4 = field.concreteType) !== null && _field$concreteType4 !== void 0 ? _field$concreteType4 : this._getRecordType(payload); var dataID = RelayModernRecord.getDataID(record); var expected = isClientID(dataID) && dataID !== ROOT_ID || RelayModernRecord.getType(record) === typeName; if (!expected) { var logEvent = { name: 'idCollision.typename', previous_typename: RelayModernRecord.getType(record), new_typename: typeName }; if (this._log != null) { this._log(logEvent); } } } if (process.env.NODE_ENV !== "production") { var _field$concreteType5; var _typeName3 = (_field$concreteType5 = field.concreteType) !== null && _field$concreteType5 !== void 0 ? _field$concreteType5 : this._getRecordType(payload); var _dataID = RelayModernRecord.getDataID(record); var _expected = isClientID(_dataID) && _dataID !== ROOT_ID || RelayModernRecord.getType(record) === _typeName3; process.env.NODE_ENV !== "production" ? warning(_expected, 'RelayResponseNormalizer: Invalid record `%s`. Expected %s to 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.', _dataID, TYPENAME_KEY, RelayModernRecord.getType(record), _typeName3) : void 0; } }; _proto._validateConflictingFieldsWithIdenticalId = function _validateConflictingFieldsWithIdenticalId(record, storageKey, fieldValue) { if (process.env.NODE_ENV !== "production") { var previousValue = RelayModernRecord.getValue(record, storageKey); var dataID = RelayModernRecord.getDataID(record); var expected = storageKey === TYPENAME_KEY || previousValue === undefined || areEqual(previousValue, fieldValue); process.env.NODE_ENV !== "production" ? warning(expected, 'RelayResponseNormalizer: Invalid record. The record contains two ' + 'instances of the same id: `%s` with conflicting field, %s and its values: %s and %s. ' + 'If two fields are different but share ' + 'the same id, one field will overwrite the other.', dataID, storageKey, previousValue, fieldValue) : void 0; } }; _proto._validateConflictingLinkedFieldsWithIdenticalId = function _validateConflictingLinkedFieldsWithIdenticalId(prevID, nextID, storageKey) { if (process.env.NODE_ENV !== "production") { var expected = prevID === undefined || prevID === nextID; process.env.NODE_ENV !== "production" ? warning(expected, 'RelayResponseNormalizer: Invalid record. The record contains ' + 'references to the conflicting field, %s and its id values: %s and %s. ' + 'We need to make sure that the record the field points ' + 'to remains consistent or one field will overwrite the other.', storageKey, prevID, nextID) : void 0; } }; return RelayResponseNormalizer; }(); module.exports = { normalize: normalize };