Files
personal_site/frontend/node_modules/relay-runtime/lib/store/RelayReader.js
T

740 lines
26 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
*/
// flowlint ambiguous-object-type:error
'use strict';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var ClientID = require('./ClientID');
var RelayFeatureFlags = require('../util/RelayFeatureFlags');
var RelayModernRecord = require('./RelayModernRecord');
var invariant = require('invariant');
var _require = require('../util/RelayConcreteNode'),
ACTOR_CHANGE = _require.ACTOR_CHANGE,
CLIENT_EXTENSION = _require.CLIENT_EXTENSION,
CONDITION = _require.CONDITION,
DEFER = _require.DEFER,
FLIGHT_FIELD = _require.FLIGHT_FIELD,
FRAGMENT_SPREAD = _require.FRAGMENT_SPREAD,
INLINE_DATA_FRAGMENT_SPREAD = _require.INLINE_DATA_FRAGMENT_SPREAD,
INLINE_FRAGMENT = _require.INLINE_FRAGMENT,
LINKED_FIELD = _require.LINKED_FIELD,
MODULE_IMPORT = _require.MODULE_IMPORT,
REQUIRED_FIELD = _require.REQUIRED_FIELD,
RELAY_RESOLVER = _require.RELAY_RESOLVER,
SCALAR_FIELD = _require.SCALAR_FIELD,
STREAM = _require.STREAM;
var _require2 = require('./RelayStoreReactFlightUtils'),
getReactFlightClientResponse = _require2.getReactFlightClientResponse;
var _require3 = require('./RelayStoreUtils'),
FRAGMENTS_KEY = _require3.FRAGMENTS_KEY,
FRAGMENT_OWNER_KEY = _require3.FRAGMENT_OWNER_KEY,
FRAGMENT_PROP_NAME_KEY = _require3.FRAGMENT_PROP_NAME_KEY,
ID_KEY = _require3.ID_KEY,
IS_WITHIN_UNMATCHED_TYPE_REFINEMENT = _require3.IS_WITHIN_UNMATCHED_TYPE_REFINEMENT,
MODULE_COMPONENT_KEY = _require3.MODULE_COMPONENT_KEY,
ROOT_ID = _require3.ROOT_ID,
getArgumentValues = _require3.getArgumentValues,
getStorageKey = _require3.getStorageKey,
getModuleComponentKey = _require3.getModuleComponentKey;
var _require4 = require('./ResolverCache'),
NoopResolverCache = _require4.NoopResolverCache;
var _require5 = require('./ResolverFragments'),
withResolverContext = _require5.withResolverContext;
var _require6 = require('./TypeID'),
generateTypeID = _require6.generateTypeID;
function read(recordSource, selector, resolverCache) {
var reader = new RelayReader(recordSource, selector, resolverCache !== null && resolverCache !== void 0 ? resolverCache : new NoopResolverCache());
return reader.read();
}
/**
* @private
*/
var RelayReader = /*#__PURE__*/function () {
function RelayReader(recordSource, selector, resolverCache) {
this._isMissingData = false;
this._isWithinUnmatchedTypeRefinement = false;
this._missingRequiredFields = null;
this._owner = selector.owner;
this._recordSource = recordSource;
this._seenRecords = new Set();
this._selector = selector;
this._variables = selector.variables;
this._resolverCache = resolverCache;
}
var _proto = RelayReader.prototype;
_proto.read = function read() {
var _this$_selector = this._selector,
node = _this$_selector.node,
dataID = _this$_selector.dataID,
isWithinUnmatchedTypeRefinement = _this$_selector.isWithinUnmatchedTypeRefinement;
var abstractKey = node.abstractKey;
var record = this._recordSource.get(dataID); // Relay historically allowed child fragments to be read even if the root object
// did not match the type of the fragment: either the root object has a different
// concrete type than the fragment (for concrete fragments) or the root object does
// not conform to the interface/union for abstract fragments.
// For suspense purposes, however, we want to accurately compute whether any data
// is missing: but if the fragment type doesn't match (or a parent type didn't
// match), then no data is expected to be present.
// By default data is expected to be present unless this selector was read out
// from within a non-matching type refinement in a parent fragment:
var isDataExpectedToBePresent = !isWithinUnmatchedTypeRefinement; // If this is a concrete fragment and the concrete type of the record does not
// match, then no data is expected to be present.
if (isDataExpectedToBePresent && abstractKey == null && record != null) {
var recordType = RelayModernRecord.getType(record);
if (recordType !== node.type && // The root record type is a special `__Root` type and may not match the
// type on the ast, so ignore type mismatches at the root.
// We currently detect whether we're at the root by checking against ROOT_ID,
// but this does not work for mutations/subscriptions which generate unique
// root ids. This is acceptable in practice as we don't read data for mutations/
// subscriptions in a situation where we would use isMissingData to decide whether
// to suspend or not.
// TODO T96653810: Correctly detect reading from root of mutation/subscription
dataID !== ROOT_ID) {
isDataExpectedToBePresent = false;
}
} // If this is an abstract fragment (and the precise refinement GK is enabled)
// then data is only expected to be present if the record type is known to
// implement the interface. If we aren't sure whether the record implements
// the interface, that itself constitutes "expected" data being missing.
if (isDataExpectedToBePresent && abstractKey != null && record != null) {
var _recordType = RelayModernRecord.getType(record);
var typeID = generateTypeID(_recordType);
var typeRecord = this._recordSource.get(typeID);
var implementsInterface = typeRecord != null ? RelayModernRecord.getValue(typeRecord, abstractKey) : null;
if (implementsInterface === false) {
// Type known to not implement the interface
isDataExpectedToBePresent = false;
} else if (implementsInterface == null) {
// Don't know if the type implements the interface or not
this._isMissingData = true;
}
}
this._isWithinUnmatchedTypeRefinement = !isDataExpectedToBePresent;
var data = this._traverse(node, dataID, null);
return {
data: data,
isMissingData: this._isMissingData && isDataExpectedToBePresent,
seenRecords: this._seenRecords,
selector: this._selector,
missingRequiredFields: this._missingRequiredFields
};
};
_proto._traverse = function _traverse(node, dataID, prevData) {
var record = this._recordSource.get(dataID);
this._seenRecords.add(dataID);
if (record == null) {
if (record === undefined) {
this._isMissingData = true;
}
return record;
}
var data = prevData || {};
var hadRequiredData = this._traverseSelections(node.selections, record, data);
return hadRequiredData ? data : null;
};
_proto._getVariableValue = function _getVariableValue(name) {
!this._variables.hasOwnProperty(name) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Undefined variable `%s`.', name) : invariant(false) : void 0; // $FlowFixMe[cannot-write]
return this._variables[name];
};
_proto._maybeReportUnexpectedNull = function _maybeReportUnexpectedNull(fieldPath, action, record) {
var _this$_missingRequire;
if (((_this$_missingRequire = this._missingRequiredFields) === null || _this$_missingRequire === void 0 ? void 0 : _this$_missingRequire.action) === 'THROW') {
// Chained @required directives may cause a parent `@required(action:
// THROW)` field to become null, so the first missing field we
// encounter is likely to be the root cause of the error.
return;
}
var owner = this._selector.node.name;
switch (action) {
case 'THROW':
this._missingRequiredFields = {
action: action,
field: {
path: fieldPath,
owner: owner
}
};
return;
case 'LOG':
if (this._missingRequiredFields == null) {
this._missingRequiredFields = {
action: action,
fields: []
};
}
this._missingRequiredFields.fields.push({
path: fieldPath,
owner: owner
});
return;
default:
action;
}
};
_proto._traverseSelections = function _traverseSelections(selections, record, data)
/* had all expected data */
{
for (var i = 0; i < selections.length; i++) {
var selection = selections[i];
switch (selection.kind) {
case REQUIRED_FIELD:
!RelayFeatureFlags.ENABLE_REQUIRED_DIRECTIVES ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Encountered a `@required` directive at path "%s" in `%s` without the `ENABLE_REQUIRED_DIRECTIVES` feature flag enabled.', selection.path, this._selector.node.name) : invariant(false) : void 0;
var fieldValue = this._readRequiredField(selection, record, data);
if (fieldValue == null) {
var action = selection.action;
if (action !== 'NONE') {
this._maybeReportUnexpectedNull(selection.path, action, record);
} // We are going to throw, or our parent is going to get nulled out.
// Either way, sibling values are going to be ignored, so we can
// bail early here as an optimization.
return false;
}
break;
case SCALAR_FIELD:
this._readScalar(selection, record, data);
break;
case LINKED_FIELD:
if (selection.plural) {
this._readPluralLink(selection, record, data);
} else {
this._readLink(selection, record, data);
}
break;
case CONDITION:
var conditionValue = Boolean(this._getVariableValue(selection.condition));
if (conditionValue === selection.passingValue) {
var hasExpectedData = this._traverseSelections(selection.selections, record, data);
if (!hasExpectedData) {
return false;
}
}
break;
case INLINE_FRAGMENT:
{
var abstractKey = selection.abstractKey;
if (abstractKey == null) {
// concrete type refinement: only read data if the type exactly matches
var typeName = RelayModernRecord.getType(record);
if (typeName != null && typeName === selection.type) {
var _hasExpectedData = this._traverseSelections(selection.selections, record, data);
if (!_hasExpectedData) {
return false;
}
}
} else {
// Similar to the logic in read(): data is only expected to be present
// if the record is known to conform to the interface. If we don't know
// whether the type conforms or not, that constitutes missing data.
// store flags to reset after reading
var parentIsMissingData = this._isMissingData;
var parentIsWithinUnmatchedTypeRefinement = this._isWithinUnmatchedTypeRefinement;
var _typeName = RelayModernRecord.getType(record);
var typeID = generateTypeID(_typeName);
var typeRecord = this._recordSource.get(typeID);
var implementsInterface = typeRecord != null ? RelayModernRecord.getValue(typeRecord, abstractKey) : null;
this._isWithinUnmatchedTypeRefinement = parentIsWithinUnmatchedTypeRefinement || implementsInterface === false;
this._traverseSelections(selection.selections, record, data);
this._isWithinUnmatchedTypeRefinement = parentIsWithinUnmatchedTypeRefinement;
if (implementsInterface === false) {
// Type known to not implement the interface, no data expected
this._isMissingData = parentIsMissingData;
} else if (implementsInterface == null) {
// Don't know if the type implements the interface or not
this._isMissingData = true;
}
}
break;
}
case RELAY_RESOLVER:
{
if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
throw new Error('Relay Resolver fields are not yet supported.');
}
this._readResolverField(selection, record, data);
break;
}
case FRAGMENT_SPREAD:
this._createFragmentPointer(selection, record, data);
break;
case MODULE_IMPORT:
this._readModuleImport(selection, record, data);
break;
case INLINE_DATA_FRAGMENT_SPREAD:
this._createInlineDataOrResolverFragmentPointer(selection, record, data);
break;
case DEFER:
case CLIENT_EXTENSION:
{
var isMissingData = this._isMissingData;
var _hasExpectedData2 = this._traverseSelections(selection.selections, record, data);
this._isMissingData = isMissingData;
if (!_hasExpectedData2) {
return false;
}
break;
}
case STREAM:
{
var _hasExpectedData3 = this._traverseSelections(selection.selections, record, data);
if (!_hasExpectedData3) {
return false;
}
break;
}
case FLIGHT_FIELD:
if (RelayFeatureFlags.ENABLE_REACT_FLIGHT_COMPONENT_FIELD) {
this._readFlightField(selection, record, data);
} else {
throw new Error('Flight fields are not yet supported.');
}
break;
case ACTOR_CHANGE:
this._readActorChange(selection, record, data);
break;
default:
selection;
!false ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Unexpected ast kind `%s`.', selection.kind) : invariant(false) : void 0;
}
}
return true;
};
_proto._readRequiredField = function _readRequiredField(selection, record, data) {
switch (selection.field.kind) {
case SCALAR_FIELD:
return this._readScalar(selection.field, record, data);
case LINKED_FIELD:
if (selection.field.plural) {
return this._readPluralLink(selection.field, record, data);
} else {
return this._readLink(selection.field, record, data);
}
case RELAY_RESOLVER:
if (!RelayFeatureFlags.ENABLE_RELAY_RESOLVERS) {
throw new Error('Relay Resolver fields are not yet supported.');
}
this._readResolverField(selection.field, record, data);
break;
default:
selection.field.kind;
!false ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Unexpected ast kind `%s`.', selection.kind) : invariant(false) : void 0;
}
};
_proto._readResolverField = function _readResolverField(field, record, data) {
var _this = this;
var resolverModule = field.resolverModule,
fragment = field.fragment;
var storageKey = getStorageKey(field, this._variables);
var resolverID = ClientID.generateClientID(RelayModernRecord.getDataID(record), storageKey); // Found when reading the resolver fragment, which can happen either when
// evaluating the resolver and it calls readFragment, or when checking if the
// inputs have changed since a previous evaluation:
var fragmentValue;
var fragmentReaderSelector;
var fragmentSeenRecordIDs = new Set();
var getDataForResolverFragment = function getDataForResolverFragment(singularReaderSelector) {
if (fragmentValue != null) {
// It was already read when checking for input staleness; no need to read it again.
// Note that the variables like fragmentSeenRecordIDs in the outer closure will have
// already been set and will still be used in this case.
return fragmentValue;
}
fragmentReaderSelector = singularReaderSelector;
var existingSeenRecords = _this._seenRecords;
try {
var _resolverFragmentData;
_this._seenRecords = fragmentSeenRecordIDs;
var resolverFragmentData = {};
_this._createInlineDataOrResolverFragmentPointer(singularReaderSelector.node, record, resolverFragmentData);
fragmentValue = (_resolverFragmentData = resolverFragmentData[FRAGMENTS_KEY]) === null || _resolverFragmentData === void 0 ? void 0 : _resolverFragmentData[fragment.name];
!(typeof fragmentValue === 'object' && fragmentValue !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, "Expected reader data to contain a __fragments property with a property for the fragment named ".concat(fragment.name, ", but it is missing.")) : invariant(false) : void 0;
return fragmentValue;
} finally {
_this._seenRecords = existingSeenRecords;
}
};
var resolverContext = {
getDataForResolverFragment: getDataForResolverFragment
};
var _this$_resolverCache$ = this._resolverCache.readFromCacheOrEvaluate(record, field, this._variables, function () {
var key = {
__id: RelayModernRecord.getDataID(record),
__fragmentOwner: _this._owner,
__fragments: (0, _defineProperty2["default"])({}, fragment.name, {})
};
return withResolverContext(resolverContext, function () {
// $FlowFixMe[prop-missing] - resolver module's type signature is a lie
var resolverResult = resolverModule(key);
return {
resolverResult: resolverResult,
fragmentValue: fragmentValue,
resolverID: resolverID,
seenRecordIDs: fragmentSeenRecordIDs,
readerSelector: fragmentReaderSelector
};
});
}, getDataForResolverFragment),
result = _this$_resolverCache$[0],
seenRecord = _this$_resolverCache$[1];
if (seenRecord != null) {
this._seenRecords.add(seenRecord);
}
data[storageKey] = result;
return result;
};
_proto._readFlightField = function _readFlightField(field, record, data) {
var _field$alias;
var applicationName = (_field$alias = field.alias) !== null && _field$alias !== void 0 ? _field$alias : field.name;
var storageKey = getStorageKey(field, this._variables);
var reactFlightClientResponseRecordID = RelayModernRecord.getLinkedRecordID(record, storageKey);
if (reactFlightClientResponseRecordID == null) {
data[applicationName] = reactFlightClientResponseRecordID;
if (reactFlightClientResponseRecordID === undefined) {
this._isMissingData = true;
}
return reactFlightClientResponseRecordID;
}
var reactFlightClientResponseRecord = this._recordSource.get(reactFlightClientResponseRecordID);
this._seenRecords.add(reactFlightClientResponseRecordID);
if (reactFlightClientResponseRecord == null) {
data[applicationName] = reactFlightClientResponseRecord;
if (reactFlightClientResponseRecord === undefined) {
this._isMissingData = true;
}
return reactFlightClientResponseRecord;
}
var clientResponse = getReactFlightClientResponse(reactFlightClientResponseRecord);
data[applicationName] = clientResponse;
return clientResponse;
};
_proto._readScalar = function _readScalar(field, record, data) {
var _field$alias2;
var applicationName = (_field$alias2 = field.alias) !== null && _field$alias2 !== void 0 ? _field$alias2 : field.name;
var storageKey = getStorageKey(field, this._variables);
var value = RelayModernRecord.getValue(record, storageKey);
if (value === undefined) {
this._isMissingData = true;
}
data[applicationName] = value;
return value;
};
_proto._readLink = function _readLink(field, record, data) {
var _field$alias3;
var applicationName = (_field$alias3 = field.alias) !== null && _field$alias3 !== void 0 ? _field$alias3 : field.name;
var storageKey = getStorageKey(field, this._variables);
var linkedID = RelayModernRecord.getLinkedRecordID(record, storageKey);
if (linkedID == null) {
data[applicationName] = linkedID;
if (linkedID === undefined) {
this._isMissingData = true;
}
return linkedID;
}
var prevData = data[applicationName];
!(prevData == null || typeof prevData === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Expected data for field `%s` on record `%s` ' + 'to be an object, got `%s`.', applicationName, RelayModernRecord.getDataID(record), prevData) : invariant(false) : void 0; // $FlowFixMe[incompatible-variance]
var value = this._traverse(field, linkedID, prevData);
data[applicationName] = value;
return value;
};
_proto._readActorChange = function _readActorChange(field, record, data) {
var _field$alias4;
var applicationName = (_field$alias4 = field.alias) !== null && _field$alias4 !== void 0 ? _field$alias4 : field.name;
var storageKey = getStorageKey(field, this._variables);
var externalRef = RelayModernRecord.getActorLinkedRecordID(record, storageKey);
if (externalRef == null) {
data[applicationName] = externalRef;
if (externalRef === undefined) {
this._isMissingData = true;
}
return data[applicationName];
}
var actorIdentifier = externalRef[0],
dataID = externalRef[1];
var fragmentRef = {};
this._createFragmentPointer(field.fragmentSpread, {
__id: dataID
}, fragmentRef);
data[applicationName] = {
__fragmentRef: fragmentRef,
__viewer: actorIdentifier
};
return data[applicationName];
};
_proto._readPluralLink = function _readPluralLink(field, record, data) {
var _this2 = this;
var _field$alias5;
var applicationName = (_field$alias5 = field.alias) !== null && _field$alias5 !== void 0 ? _field$alias5 : field.name;
var storageKey = getStorageKey(field, this._variables);
var linkedIDs = RelayModernRecord.getLinkedRecordIDs(record, storageKey);
if (linkedIDs == null) {
data[applicationName] = linkedIDs;
if (linkedIDs === undefined) {
this._isMissingData = true;
}
return linkedIDs;
}
var prevData = data[applicationName];
!(prevData == null || Array.isArray(prevData)) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Expected data for field `%s` on record `%s` ' + 'to be an array, got `%s`.', applicationName, RelayModernRecord.getDataID(record), prevData) : invariant(false) : void 0;
var linkedArray = prevData || [];
linkedIDs.forEach(function (linkedID, nextIndex) {
if (linkedID == null) {
if (linkedID === undefined) {
_this2._isMissingData = true;
} // $FlowFixMe[cannot-write]
linkedArray[nextIndex] = linkedID;
return;
}
var prevItem = linkedArray[nextIndex];
!(prevItem == null || typeof prevItem === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader(): Expected data for field `%s` on record `%s` ' + 'to be an object, got `%s`.', applicationName, RelayModernRecord.getDataID(record), prevItem) : invariant(false) : void 0; // $FlowFixMe[cannot-write]
// $FlowFixMe[incompatible-variance]
linkedArray[nextIndex] = _this2._traverse(field, linkedID, prevItem);
});
data[applicationName] = linkedArray;
return linkedArray;
}
/**
* Reads a ReaderModuleImport, which was generated from using the @module
* directive.
*/
;
_proto._readModuleImport = function _readModuleImport(moduleImport, record, data) {
// Determine the component module from the store: if the field is missing
// it means we don't know what component to render the match with.
var componentKey = getModuleComponentKey(moduleImport.documentName);
var component = RelayModernRecord.getValue(record, componentKey);
if (component == null) {
if (component === undefined) {
this._isMissingData = true;
}
return;
} // Otherwise, read the fragment and module associated to the concrete
// type, and put that data with the result:
// - For the matched fragment, create the relevant fragment pointer and add
// the expected fragmentPropName
// - For the matched module, create a reference to the module
this._createFragmentPointer({
kind: 'FragmentSpread',
name: moduleImport.fragmentName,
args: moduleImport.args
}, record, data);
data[FRAGMENT_PROP_NAME_KEY] = moduleImport.fragmentPropName;
data[MODULE_COMPONENT_KEY] = component;
};
_proto._createFragmentPointer = function _createFragmentPointer(fragmentSpread, record, data) {
var fragmentPointers = data[FRAGMENTS_KEY];
if (fragmentPointers == null) {
fragmentPointers = data[FRAGMENTS_KEY] = {};
}
!(typeof fragmentPointers === 'object' && fragmentPointers != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader: Expected fragment spread data to be an object, got `%s`.', fragmentPointers) : invariant(false) : void 0;
if (data[ID_KEY] == null) {
data[ID_KEY] = RelayModernRecord.getDataID(record);
} // $FlowFixMe[cannot-write] - writing into read-only field
fragmentPointers[fragmentSpread.name] = fragmentSpread.args ? getArgumentValues(fragmentSpread.args, this._variables) : {};
data[FRAGMENT_OWNER_KEY] = this._owner;
data[IS_WITHIN_UNMATCHED_TYPE_REFINEMENT] = this._isWithinUnmatchedTypeRefinement;
};
_proto._createInlineDataOrResolverFragmentPointer = function _createInlineDataOrResolverFragmentPointer(fragmentSpreadOrFragment, record, data) {
var fragmentPointers = data[FRAGMENTS_KEY];
if (fragmentPointers == null) {
fragmentPointers = data[FRAGMENTS_KEY] = {};
}
!(typeof fragmentPointers === 'object' && fragmentPointers != null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'RelayReader: Expected fragment spread data to be an object, got `%s`.', fragmentPointers) : invariant(false) : void 0;
if (data[ID_KEY] == null) {
data[ID_KEY] = RelayModernRecord.getDataID(record);
}
var inlineData = {};
this._traverseSelections(fragmentSpreadOrFragment.selections, record, inlineData); // $FlowFixMe[cannot-write] - writing into read-only field
fragmentPointers[fragmentSpreadOrFragment.name] = inlineData;
};
return RelayReader;
}();
module.exports = {
read: read
};