/** * 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'; /** * @public * * Given the result `item` from a parent that fetched `fragment`, creates a * selector that can be used to read the results of that fragment for that item. * * Example: * * Given two fragments as follows: * * ``` * fragment Parent on User { * id * ...Child * } * fragment Child on User { * name * } * ``` * * And given some object `parent` that is the results of `Parent` for id "4", * the results of `Child` can be accessed by first getting a selector and then * using that selector to `lookup()` the results against the environment: * * ``` * const childSelector = getSelector(queryVariables, Child, parent); * const childData = environment.lookup(childSelector).data; * ``` */ function getSelector(operationVariables, fragment, item) { !(typeof item === 'object' && item !== null && !Array.isArray(item)) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernSelector: Expected value for fragment `%s` to be an object, got ' + '`%s`.', fragment.name, JSON.stringify(item)) : require("fbjs/lib/invariant")(false) : void 0; var dataID = item[require("./RelayStoreUtils").ID_KEY]; var fragments = item[require("./RelayStoreUtils").FRAGMENTS_KEY]; var owner = item[require("./RelayStoreUtils").FRAGMENT_OWNER_KEY]; if (typeof dataID === 'string' && typeof fragments === 'object' && fragments !== null && typeof fragments[fragment.name] === 'object' && fragments[fragment.name] !== null) { var argumentVariables = fragments[fragment.name]; if (owner != null && typeof owner === 'object') { // $FlowFixMe - TODO T39154660 var typedOwner = owner; var ownerOperationVariables = typedOwner.variables; var _fragmentVariables = require("./RelayConcreteVariables").getFragmentVariables(fragment, ownerOperationVariables, argumentVariables); return { owner: typedOwner, selector: { dataID: dataID, node: fragment, variables: _fragmentVariables } }; } var fragmentVariables = require("./RelayConcreteVariables").getFragmentVariables(fragment, operationVariables, argumentVariables); return { owner: null, selector: { dataID: dataID, node: fragment, variables: fragmentVariables } }; } process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(false, 'RelayModernSelector: Expected object to contain data for fragment `%s`, got ' + '`%s`. Make sure that the parent operation/fragment included fragment ' + '`...%s` without `@relay(mask: false)`.', fragment.name, JSON.stringify(item), fragment.name) : void 0; return null; } /** * @public * * Given the result `items` from a parent that fetched `fragment`, creates a * selector that can be used to read the results of that fragment on those * items. This is similar to `getSelector` but for "plural" fragments that * expect an array of results and therefore return an array of selectors. */ function getSelectorList(operationVariables, fragment, items) { var selectors = null; items.forEach(function (item) { var selector = item != null ? getSelector(operationVariables, fragment, item) : null; if (selector != null) { selectors = selectors || []; selectors.push(selector); } }); return selectors; } /** * @public * * Given a mapping of keys -> results and a mapping of keys -> fragments, * extracts the selectors for those fragments from the results. * * The canonical use-case for this function is ReactRelayFragmentContainer, which * uses this function to convert (props, fragments) into selectors so that it * can read the results to pass to the inner component. */ function getSelectorsFromObject(operationVariables, fragments, object) { var selectors = {}; for (var _key in fragments) { if (fragments.hasOwnProperty(_key)) { var fragment = fragments[_key]; var item = object[_key]; if (item == null) { selectors[_key] = item; } else if (fragment.metadata && fragment.metadata.plural === true) { !Array.isArray(item) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernSelector: Expected value for key `%s` to be an array, got `%s`. ' + 'Remove `@relay(plural: true)` from fragment `%s` to allow the prop to be an object.', _key, JSON.stringify(item), fragment.name) : require("fbjs/lib/invariant")(false) : void 0; selectors[_key] = getSelectorList(operationVariables, fragment, item); } else { !!Array.isArray(item) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernFragmentSpecResolver: Expected value for key `%s` to be an object, got `%s`. ' + 'Add `@relay(plural: true)` to fragment `%s` to allow the prop to be an array of items.', _key, JSON.stringify(item), fragment.name) : require("fbjs/lib/invariant")(false) : void 0; selectors[_key] = getSelector(operationVariables, fragment, item); } } } return selectors; } /** * @public * * Given a mapping of keys -> results and a mapping of keys -> fragments, * extracts a mapping of keys -> id(s) of the results. * * Similar to `getSelectorsFromObject()`, this function can be useful in * determining the "identity" of the props passed to a component. */ function getDataIDsFromObject(fragments, object) { var ids = {}; for (var _key2 in fragments) { if (fragments.hasOwnProperty(_key2)) { var fragment = fragments[_key2]; var item = object[_key2]; if (item == null) { ids[_key2] = item; } else if (fragment.metadata && fragment.metadata.plural === true) { !Array.isArray(item) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernSelector: Expected value for key `%s` to be an array, got `%s`. ' + 'Remove `@relay(plural: true)` from fragment `%s` to allow the prop to be an object.', _key2, JSON.stringify(item), fragment.name) : require("fbjs/lib/invariant")(false) : void 0; ids[_key2] = getDataIDs(fragment, item); } else { !!Array.isArray(item) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernFragmentSpecResolver: Expected value for key `%s` to be an object, got `%s`. ' + 'Add `@relay(plural: true)` to fragment `%s` to allow the prop to be an array of items.', _key2, JSON.stringify(item), fragment.name) : require("fbjs/lib/invariant")(false) : void 0; ids[_key2] = getDataID(fragment, item); } } } return ids; } /** * @internal */ function getDataIDs(fragment, items) { var ids; items.forEach(function (item) { var id = item != null ? getDataID(fragment, item) : null; if (id != null) { ids = ids || []; ids.push(id); } }); return ids || null; } /** * @internal */ function getDataID(fragment, item) { !(typeof item === 'object' && item !== null && !Array.isArray(item)) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernSelector: Expected value for fragment `%s` to be an object, got ' + '`%s`.', fragment.name, JSON.stringify(item)) : require("fbjs/lib/invariant")(false) : void 0; var dataID = item[require("./RelayStoreUtils").ID_KEY]; if (typeof dataID === 'string') { return dataID; } process.env.NODE_ENV !== "production" ? require("fbjs/lib/warning")(false, 'RelayModernSelector: Expected object to contain data for fragment `%s`, got ' + '`%s`. Make sure that the parent operation/fragment included fragment ' + '`...%s` without `@relay(mask: false)`.', fragment.name, JSON.stringify(item), fragment.name) : void 0; return null; } /** * @public * * Given a mapping of keys -> results and a mapping of keys -> fragments, * extracts the merged variables that would be in scope for those * fragments/results. * * This can be useful in determing what varaibles were used to fetch the data * for a Relay container, for example. */ function getVariablesFromObject(operationVariables, fragments, object) { var variables = {}; for (var _key3 in fragments) { if (fragments.hasOwnProperty(_key3)) { var _ret = function () { var fragment = fragments[_key3]; var item = object[_key3]; if (item == null) { return "continue"; } else if (fragment.metadata && fragment.metadata.plural === true) { !Array.isArray(item) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernSelector: Expected value for key `%s` to be an array, got `%s`. ' + 'Remove `@relay(plural: true)` from fragment `%s` to allow the prop to be an object.', _key3, JSON.stringify(item), fragment.name) : require("fbjs/lib/invariant")(false) : void 0; item.forEach(function (value) { if (value != null) { var itemVariables = getVariables(operationVariables, fragment, value); if (itemVariables) { Object.assign(variables, itemVariables); } } }); } else { !!Array.isArray(item) ? process.env.NODE_ENV !== "production" ? require("fbjs/lib/invariant")(false, 'RelayModernFragmentSpecResolver: Expected value for key `%s` to be an object, got `%s`. ' + 'Add `@relay(plural: true)` to fragment `%s` to allow the prop to be an array of items.', _key3, JSON.stringify(item), fragment.name) : require("fbjs/lib/invariant")(false) : void 0; var itemVariables = getVariables(operationVariables, fragment, item); if (itemVariables) { Object.assign(variables, itemVariables); } } }(); if (_ret === "continue") continue; } } return variables; } /** * @internal */ function getVariables(operationVariables, fragment, item) { var ownedSelector = getSelector(operationVariables, fragment, item); if (!ownedSelector) { return null; } return ownedSelector.selector.variables; } /** * @public * * Determine if two selectors are equal (represent the same selection). Note * that this function returns `false` when the two queries/fragments are * different objects, even if they select the same fields. */ function areEqualSelectors(thisSelector, thatSelector) { return thisSelector.selector.dataID === thatSelector.selector.dataID && thisSelector.selector.node === thatSelector.selector.node && require("fbjs/lib/areEqual")(thisSelector.selector.variables, thatSelector.selector.variables); } module.exports = { areEqualSelectors: areEqualSelectors, getDataIDsFromObject: getDataIDsFromObject, getSelector: getSelector, getSelectorList: getSelectorList, getSelectorsFromObject: getSelectorsFromObject, getVariablesFromObject: getVariablesFromObject };