You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
601 lines
31 KiB
601 lines
31 KiB
import { __assign, __rest } from "tslib"; |
|
import { invariant, newInvariantError } from "../../utilities/globals/index.js"; |
|
import { storeKeyNameFromField, argumentsObjectFromField, isReference, getStoreKeyName, isNonNullObject, stringifyForDisplay, } from "../../utilities/index.js"; |
|
import { hasOwn, fieldNameFromStoreName, storeValueIsStoreObject, selectionSetMatchesResult, TypeOrFieldNameRegExp, defaultDataIdFromObject, isArray, } from "./helpers.js"; |
|
import { cacheSlot } from "./reactiveVars.js"; |
|
import { keyArgsFnFromSpecifier, keyFieldsFnFromSpecifier, } from "./key-extractor.js"; |
|
function argsFromFieldSpecifier(spec) { |
|
return (spec.args !== void 0 ? spec.args |
|
: spec.field ? argumentsObjectFromField(spec.field, spec.variables) |
|
: null); |
|
} |
|
var nullKeyFieldsFn = function () { return void 0; }; |
|
var simpleKeyArgsFn = function (_args, context) { return context.fieldName; }; |
|
// These merge functions can be selected by specifying merge:true or |
|
// merge:false in a field policy. |
|
var mergeTrueFn = function (existing, incoming, _a) { |
|
var mergeObjects = _a.mergeObjects; |
|
return mergeObjects(existing, incoming); |
|
}; |
|
var mergeFalseFn = function (_, incoming) { return incoming; }; |
|
var Policies = /** @class */ (function () { |
|
function Policies(config) { |
|
this.config = config; |
|
this.typePolicies = Object.create(null); |
|
this.toBeAdded = Object.create(null); |
|
// Map from subtype names to sets of supertype names. Note that this |
|
// representation inverts the structure of possibleTypes (whose keys are |
|
// supertypes and whose values are arrays of subtypes) because it tends |
|
// to be much more efficient to search upwards than downwards. |
|
this.supertypeMap = new Map(); |
|
// Any fuzzy subtypes specified by possibleTypes will be converted to |
|
// RegExp objects and recorded here. Every key of this map can also be |
|
// found in supertypeMap. In many cases this Map will be empty, which |
|
// means no fuzzy subtype checking will happen in fragmentMatches. |
|
this.fuzzySubtypes = new Map(); |
|
this.rootIdsByTypename = Object.create(null); |
|
this.rootTypenamesById = Object.create(null); |
|
this.usingPossibleTypes = false; |
|
this.config = __assign({ dataIdFromObject: defaultDataIdFromObject }, config); |
|
this.cache = this.config.cache; |
|
this.setRootTypename("Query"); |
|
this.setRootTypename("Mutation"); |
|
this.setRootTypename("Subscription"); |
|
if (config.possibleTypes) { |
|
this.addPossibleTypes(config.possibleTypes); |
|
} |
|
if (config.typePolicies) { |
|
this.addTypePolicies(config.typePolicies); |
|
} |
|
} |
|
Policies.prototype.identify = function (object, partialContext) { |
|
var _a; |
|
var policies = this; |
|
var typename = (partialContext && |
|
(partialContext.typename || ((_a = partialContext.storeObject) === null || _a === void 0 ? void 0 : _a.__typename))) || |
|
object.__typename; |
|
// It should be possible to write root Query fields with writeFragment, |
|
// using { __typename: "Query", ... } as the data, but it does not make |
|
// sense to allow the same identification behavior for the Mutation and |
|
// Subscription types, since application code should never be writing |
|
// directly to (or reading directly from) those root objects. |
|
if (typename === this.rootTypenamesById.ROOT_QUERY) { |
|
return ["ROOT_QUERY"]; |
|
} |
|
// Default context.storeObject to object if not otherwise provided. |
|
var storeObject = (partialContext && partialContext.storeObject) || object; |
|
var context = __assign(__assign({}, partialContext), { typename: typename, storeObject: storeObject, readField: (partialContext && partialContext.readField) || |
|
function () { |
|
var options = normalizeReadFieldOptions(arguments, storeObject); |
|
return policies.readField(options, { |
|
store: policies.cache["data"], |
|
variables: options.variables, |
|
}); |
|
} }); |
|
var id; |
|
var policy = typename && this.getTypePolicy(typename); |
|
var keyFn = (policy && policy.keyFn) || this.config.dataIdFromObject; |
|
while (keyFn) { |
|
var specifierOrId = keyFn(__assign(__assign({}, object), storeObject), context); |
|
if (isArray(specifierOrId)) { |
|
keyFn = keyFieldsFnFromSpecifier(specifierOrId); |
|
} |
|
else { |
|
id = specifierOrId; |
|
break; |
|
} |
|
} |
|
id = id ? String(id) : void 0; |
|
return context.keyObject ? [id, context.keyObject] : [id]; |
|
}; |
|
Policies.prototype.addTypePolicies = function (typePolicies) { |
|
var _this = this; |
|
Object.keys(typePolicies).forEach(function (typename) { |
|
var _a = typePolicies[typename], queryType = _a.queryType, mutationType = _a.mutationType, subscriptionType = _a.subscriptionType, incoming = __rest(_a, ["queryType", "mutationType", "subscriptionType"]); |
|
// Though {query,mutation,subscription}Type configurations are rare, |
|
// it's important to call setRootTypename as early as possible, |
|
// since these configurations should apply consistently for the |
|
// entire lifetime of the cache. Also, since only one __typename can |
|
// qualify as one of these root types, these three properties cannot |
|
// be inherited, unlike the rest of the incoming properties. That |
|
// restriction is convenient, because the purpose of this.toBeAdded |
|
// is to delay the processing of type/field policies until the first |
|
// time they're used, allowing policies to be added in any order as |
|
// long as all relevant policies (including policies for supertypes) |
|
// have been added by the time a given policy is used for the first |
|
// time. In other words, since inheritance doesn't matter for these |
|
// properties, there's also no need to delay their processing using |
|
// the this.toBeAdded queue. |
|
if (queryType) |
|
_this.setRootTypename("Query", typename); |
|
if (mutationType) |
|
_this.setRootTypename("Mutation", typename); |
|
if (subscriptionType) |
|
_this.setRootTypename("Subscription", typename); |
|
if (hasOwn.call(_this.toBeAdded, typename)) { |
|
_this.toBeAdded[typename].push(incoming); |
|
} |
|
else { |
|
_this.toBeAdded[typename] = [incoming]; |
|
} |
|
}); |
|
}; |
|
Policies.prototype.updateTypePolicy = function (typename, incoming) { |
|
var _this = this; |
|
var existing = this.getTypePolicy(typename); |
|
var keyFields = incoming.keyFields, fields = incoming.fields; |
|
function setMerge(existing, merge) { |
|
existing.merge = |
|
typeof merge === "function" ? merge |
|
// Pass merge:true as a shorthand for a merge implementation |
|
// that returns options.mergeObjects(existing, incoming). |
|
: merge === true ? mergeTrueFn |
|
// Pass merge:false to make incoming always replace existing |
|
// without any warnings about data clobbering. |
|
: merge === false ? mergeFalseFn |
|
: existing.merge; |
|
} |
|
// Type policies can define merge functions, as an alternative to |
|
// using field policies to merge child objects. |
|
setMerge(existing, incoming.merge); |
|
existing.keyFn = |
|
// Pass false to disable normalization for this typename. |
|
keyFields === false ? nullKeyFieldsFn |
|
// Pass an array of strings to use those fields to compute a |
|
// composite ID for objects of this typename. |
|
: isArray(keyFields) ? keyFieldsFnFromSpecifier(keyFields) |
|
// Pass a function to take full control over identification. |
|
: typeof keyFields === "function" ? keyFields |
|
// Leave existing.keyFn unchanged if above cases fail. |
|
: existing.keyFn; |
|
if (fields) { |
|
Object.keys(fields).forEach(function (fieldName) { |
|
var existing = _this.getFieldPolicy(typename, fieldName, true); |
|
var incoming = fields[fieldName]; |
|
if (typeof incoming === "function") { |
|
existing.read = incoming; |
|
} |
|
else { |
|
var keyArgs = incoming.keyArgs, read = incoming.read, merge = incoming.merge; |
|
existing.keyFn = |
|
// Pass false to disable argument-based differentiation of |
|
// field identities. |
|
keyArgs === false ? simpleKeyArgsFn |
|
// Pass an array of strings to use named arguments to |
|
// compute a composite identity for the field. |
|
: isArray(keyArgs) ? keyArgsFnFromSpecifier(keyArgs) |
|
// Pass a function to take full control over field identity. |
|
: typeof keyArgs === "function" ? keyArgs |
|
// Leave existing.keyFn unchanged if above cases fail. |
|
: existing.keyFn; |
|
if (typeof read === "function") { |
|
existing.read = read; |
|
} |
|
setMerge(existing, merge); |
|
} |
|
if (existing.read && existing.merge) { |
|
// If we have both a read and a merge function, assume |
|
// keyArgs:false, because read and merge together can take |
|
// responsibility for interpreting arguments in and out. This |
|
// default assumption can always be overridden by specifying |
|
// keyArgs explicitly in the FieldPolicy. |
|
existing.keyFn = existing.keyFn || simpleKeyArgsFn; |
|
} |
|
}); |
|
} |
|
}; |
|
Policies.prototype.setRootTypename = function (which, typename) { |
|
if (typename === void 0) { typename = which; } |
|
var rootId = "ROOT_" + which.toUpperCase(); |
|
var old = this.rootTypenamesById[rootId]; |
|
if (typename !== old) { |
|
invariant(!old || old === which, 5, which); |
|
// First, delete any old __typename associated with this rootId from |
|
// rootIdsByTypename. |
|
if (old) |
|
delete this.rootIdsByTypename[old]; |
|
// Now make this the only __typename that maps to this rootId. |
|
this.rootIdsByTypename[typename] = rootId; |
|
// Finally, update the __typename associated with this rootId. |
|
this.rootTypenamesById[rootId] = typename; |
|
} |
|
}; |
|
Policies.prototype.addPossibleTypes = function (possibleTypes) { |
|
var _this = this; |
|
this.usingPossibleTypes = true; |
|
Object.keys(possibleTypes).forEach(function (supertype) { |
|
// Make sure all types have an entry in this.supertypeMap, even if |
|
// their supertype set is empty, so we can return false immediately |
|
// from policies.fragmentMatches for unknown supertypes. |
|
_this.getSupertypeSet(supertype, true); |
|
possibleTypes[supertype].forEach(function (subtype) { |
|
_this.getSupertypeSet(subtype, true).add(supertype); |
|
var match = subtype.match(TypeOrFieldNameRegExp); |
|
if (!match || match[0] !== subtype) { |
|
// TODO Don't interpret just any invalid typename as a RegExp. |
|
_this.fuzzySubtypes.set(subtype, new RegExp(subtype)); |
|
} |
|
}); |
|
}); |
|
}; |
|
Policies.prototype.getTypePolicy = function (typename) { |
|
var _this = this; |
|
if (!hasOwn.call(this.typePolicies, typename)) { |
|
var policy_1 = (this.typePolicies[typename] = Object.create(null)); |
|
policy_1.fields = Object.create(null); |
|
// When the TypePolicy for typename is first accessed, instead of |
|
// starting with an empty policy object, inherit any properties or |
|
// fields from the type policies of the supertypes of typename. |
|
// |
|
// Any properties or fields defined explicitly within the TypePolicy |
|
// for typename will take precedence, and if there are multiple |
|
// supertypes, the properties of policies whose types were added |
|
// later via addPossibleTypes will take precedence over those of |
|
// earlier supertypes. TODO Perhaps we should warn about these |
|
// conflicts in development, and recommend defining the property |
|
// explicitly in the subtype policy? |
|
// |
|
// Field policy inheritance is atomic/shallow: you can't inherit a |
|
// field policy and then override just its read function, since read |
|
// and merge functions often need to cooperate, so changing only one |
|
// of them would be a recipe for inconsistency. |
|
// |
|
// Once the TypePolicy for typename has been accessed, its properties can |
|
// still be updated directly using addTypePolicies, but future changes to |
|
// inherited supertype policies will not be reflected in this subtype |
|
// policy, because this code runs at most once per typename. |
|
var supertypes_1 = this.supertypeMap.get(typename); |
|
if (!supertypes_1 && this.fuzzySubtypes.size) { |
|
// To make the inheritance logic work for unknown typename strings that |
|
// may have fuzzy supertypes, we give this typename an empty supertype |
|
// set and then populate it with any fuzzy supertypes that match. |
|
supertypes_1 = this.getSupertypeSet(typename, true); |
|
// This only works for typenames that are directly matched by a fuzzy |
|
// supertype. What if there is an intermediate chain of supertypes? |
|
// While possible, that situation can only be solved effectively by |
|
// specifying the intermediate relationships via possibleTypes, manually |
|
// and in a non-fuzzy way. |
|
this.fuzzySubtypes.forEach(function (regExp, fuzzy) { |
|
if (regExp.test(typename)) { |
|
// The fuzzy parameter is just the original string version of regExp |
|
// (not a valid __typename string), but we can look up the |
|
// associated supertype(s) in this.supertypeMap. |
|
var fuzzySupertypes = _this.supertypeMap.get(fuzzy); |
|
if (fuzzySupertypes) { |
|
fuzzySupertypes.forEach(function (supertype) { |
|
return supertypes_1.add(supertype); |
|
}); |
|
} |
|
} |
|
}); |
|
} |
|
if (supertypes_1 && supertypes_1.size) { |
|
supertypes_1.forEach(function (supertype) { |
|
var _a = _this.getTypePolicy(supertype), fields = _a.fields, rest = __rest(_a, ["fields"]); |
|
Object.assign(policy_1, rest); |
|
Object.assign(policy_1.fields, fields); |
|
}); |
|
} |
|
} |
|
var inbox = this.toBeAdded[typename]; |
|
if (inbox && inbox.length) { |
|
// Merge the pending policies into this.typePolicies, in the order they |
|
// were originally passed to addTypePolicy. |
|
inbox.splice(0).forEach(function (policy) { |
|
_this.updateTypePolicy(typename, policy); |
|
}); |
|
} |
|
return this.typePolicies[typename]; |
|
}; |
|
Policies.prototype.getFieldPolicy = function (typename, fieldName, createIfMissing) { |
|
if (typename) { |
|
var fieldPolicies = this.getTypePolicy(typename).fields; |
|
return (fieldPolicies[fieldName] || |
|
(createIfMissing && (fieldPolicies[fieldName] = Object.create(null)))); |
|
} |
|
}; |
|
Policies.prototype.getSupertypeSet = function (subtype, createIfMissing) { |
|
var supertypeSet = this.supertypeMap.get(subtype); |
|
if (!supertypeSet && createIfMissing) { |
|
this.supertypeMap.set(subtype, (supertypeSet = new Set())); |
|
} |
|
return supertypeSet; |
|
}; |
|
Policies.prototype.fragmentMatches = function (fragment, typename, result, variables) { |
|
var _this = this; |
|
if (!fragment.typeCondition) |
|
return true; |
|
// If the fragment has a type condition but the object we're matching |
|
// against does not have a __typename, the fragment cannot match. |
|
if (!typename) |
|
return false; |
|
var supertype = fragment.typeCondition.name.value; |
|
// Common case: fragment type condition and __typename are the same. |
|
if (typename === supertype) |
|
return true; |
|
if (this.usingPossibleTypes && this.supertypeMap.has(supertype)) { |
|
var typenameSupertypeSet = this.getSupertypeSet(typename, true); |
|
var workQueue_1 = [typenameSupertypeSet]; |
|
var maybeEnqueue_1 = function (subtype) { |
|
var supertypeSet = _this.getSupertypeSet(subtype, false); |
|
if (supertypeSet && |
|
supertypeSet.size && |
|
workQueue_1.indexOf(supertypeSet) < 0) { |
|
workQueue_1.push(supertypeSet); |
|
} |
|
}; |
|
// We need to check fuzzy subtypes only if we encountered fuzzy |
|
// subtype strings in addPossibleTypes, and only while writing to |
|
// the cache, since that's when selectionSetMatchesResult gives a |
|
// strong signal of fragment matching. The StoreReader class calls |
|
// policies.fragmentMatches without passing a result object, so |
|
// needToCheckFuzzySubtypes is always false while reading. |
|
var needToCheckFuzzySubtypes = !!(result && this.fuzzySubtypes.size); |
|
var checkingFuzzySubtypes = false; |
|
// It's important to keep evaluating workQueue.length each time through |
|
// the loop, because the queue can grow while we're iterating over it. |
|
for (var i = 0; i < workQueue_1.length; ++i) { |
|
var supertypeSet = workQueue_1[i]; |
|
if (supertypeSet.has(supertype)) { |
|
if (!typenameSupertypeSet.has(supertype)) { |
|
if (checkingFuzzySubtypes) { |
|
globalThis.__DEV__ !== false && invariant.warn(6, typename, supertype); |
|
} |
|
// Record positive results for faster future lookup. |
|
// Unfortunately, we cannot safely cache negative results, |
|
// because new possibleTypes data could always be added to the |
|
// Policies class. |
|
typenameSupertypeSet.add(supertype); |
|
} |
|
return true; |
|
} |
|
supertypeSet.forEach(maybeEnqueue_1); |
|
if (needToCheckFuzzySubtypes && |
|
// Start checking fuzzy subtypes only after exhausting all |
|
// non-fuzzy subtypes (after the final iteration of the loop). |
|
i === workQueue_1.length - 1 && |
|
// We could wait to compare fragment.selectionSet to result |
|
// after we verify the supertype, but this check is often less |
|
// expensive than that search, and we will have to do the |
|
// comparison anyway whenever we find a potential match. |
|
selectionSetMatchesResult(fragment.selectionSet, result, variables)) { |
|
// We don't always need to check fuzzy subtypes (if no result |
|
// was provided, or !this.fuzzySubtypes.size), but, when we do, |
|
// we only want to check them once. |
|
needToCheckFuzzySubtypes = false; |
|
checkingFuzzySubtypes = true; |
|
// If we find any fuzzy subtypes that match typename, extend the |
|
// workQueue to search through the supertypes of those fuzzy |
|
// subtypes. Otherwise the for-loop will terminate and we'll |
|
// return false below. |
|
this.fuzzySubtypes.forEach(function (regExp, fuzzyString) { |
|
var match = typename.match(regExp); |
|
if (match && match[0] === typename) { |
|
maybeEnqueue_1(fuzzyString); |
|
} |
|
}); |
|
} |
|
} |
|
} |
|
return false; |
|
}; |
|
Policies.prototype.hasKeyArgs = function (typename, fieldName) { |
|
var policy = this.getFieldPolicy(typename, fieldName, false); |
|
return !!(policy && policy.keyFn); |
|
}; |
|
Policies.prototype.getStoreFieldName = function (fieldSpec) { |
|
var typename = fieldSpec.typename, fieldName = fieldSpec.fieldName; |
|
var policy = this.getFieldPolicy(typename, fieldName, false); |
|
var storeFieldName; |
|
var keyFn = policy && policy.keyFn; |
|
if (keyFn && typename) { |
|
var context = { |
|
typename: typename, |
|
fieldName: fieldName, |
|
field: fieldSpec.field || null, |
|
variables: fieldSpec.variables, |
|
}; |
|
var args = argsFromFieldSpecifier(fieldSpec); |
|
while (keyFn) { |
|
var specifierOrString = keyFn(args, context); |
|
if (isArray(specifierOrString)) { |
|
keyFn = keyArgsFnFromSpecifier(specifierOrString); |
|
} |
|
else { |
|
// If the custom keyFn returns a falsy value, fall back to |
|
// fieldName instead. |
|
storeFieldName = specifierOrString || fieldName; |
|
break; |
|
} |
|
} |
|
} |
|
if (storeFieldName === void 0) { |
|
storeFieldName = |
|
fieldSpec.field ? |
|
storeKeyNameFromField(fieldSpec.field, fieldSpec.variables) |
|
: getStoreKeyName(fieldName, argsFromFieldSpecifier(fieldSpec)); |
|
} |
|
// Returning false from a keyArgs function is like configuring |
|
// keyArgs: false, but more dynamic. |
|
if (storeFieldName === false) { |
|
return fieldName; |
|
} |
|
// Make sure custom field names start with the actual field.name.value |
|
// of the field, so we can always figure out which properties of a |
|
// StoreObject correspond to which original field names. |
|
return fieldName === fieldNameFromStoreName(storeFieldName) ? storeFieldName |
|
: fieldName + ":" + storeFieldName; |
|
}; |
|
Policies.prototype.readField = function (options, context) { |
|
var objectOrReference = options.from; |
|
if (!objectOrReference) |
|
return; |
|
var nameOrField = options.field || options.fieldName; |
|
if (!nameOrField) |
|
return; |
|
if (options.typename === void 0) { |
|
var typename = context.store.getFieldValue(objectOrReference, "__typename"); |
|
if (typename) |
|
options.typename = typename; |
|
} |
|
var storeFieldName = this.getStoreFieldName(options); |
|
var fieldName = fieldNameFromStoreName(storeFieldName); |
|
var existing = context.store.getFieldValue(objectOrReference, storeFieldName); |
|
var policy = this.getFieldPolicy(options.typename, fieldName, false); |
|
var read = policy && policy.read; |
|
if (read) { |
|
var readOptions = makeFieldFunctionOptions(this, objectOrReference, options, context, context.store.getStorage(isReference(objectOrReference) ? |
|
objectOrReference.__ref |
|
: objectOrReference, storeFieldName)); |
|
// Call read(existing, readOptions) with cacheSlot holding this.cache. |
|
return cacheSlot.withValue(this.cache, read, [ |
|
existing, |
|
readOptions, |
|
]); |
|
} |
|
return existing; |
|
}; |
|
Policies.prototype.getReadFunction = function (typename, fieldName) { |
|
var policy = this.getFieldPolicy(typename, fieldName, false); |
|
return policy && policy.read; |
|
}; |
|
Policies.prototype.getMergeFunction = function (parentTypename, fieldName, childTypename) { |
|
var policy = this.getFieldPolicy(parentTypename, fieldName, false); |
|
var merge = policy && policy.merge; |
|
if (!merge && childTypename) { |
|
policy = this.getTypePolicy(childTypename); |
|
merge = policy && policy.merge; |
|
} |
|
return merge; |
|
}; |
|
Policies.prototype.runMergeFunction = function (existing, incoming, _a, context, storage) { |
|
var field = _a.field, typename = _a.typename, merge = _a.merge; |
|
if (merge === mergeTrueFn) { |
|
// Instead of going to the trouble of creating a full |
|
// FieldFunctionOptions object and calling mergeTrueFn, we can |
|
// simply call mergeObjects, as mergeTrueFn would. |
|
return makeMergeObjectsFunction(context.store)(existing, incoming); |
|
} |
|
if (merge === mergeFalseFn) { |
|
// Likewise for mergeFalseFn, whose implementation is even simpler. |
|
return incoming; |
|
} |
|
// If cache.writeQuery or cache.writeFragment was called with |
|
// options.overwrite set to true, we still call merge functions, but |
|
// the existing data is always undefined, so the merge function will |
|
// not attempt to combine the incoming data with the existing data. |
|
if (context.overwrite) { |
|
existing = void 0; |
|
} |
|
return merge(existing, incoming, makeFieldFunctionOptions(this, |
|
// Unlike options.readField for read functions, we do not fall |
|
// back to the current object if no foreignObjOrRef is provided, |
|
// because it's not clear what the current object should be for |
|
// merge functions: the (possibly undefined) existing object, or |
|
// the incoming object? If you think your merge function needs |
|
// to read sibling fields in order to produce a new value for |
|
// the current field, you might want to rethink your strategy, |
|
// because that's a recipe for making merge behavior sensitive |
|
// to the order in which fields are written into the cache. |
|
// However, readField(name, ref) is useful for merge functions |
|
// that need to deduplicate child objects and references. |
|
void 0, { |
|
typename: typename, |
|
fieldName: field.name.value, |
|
field: field, |
|
variables: context.variables, |
|
}, context, storage || Object.create(null))); |
|
}; |
|
return Policies; |
|
}()); |
|
export { Policies }; |
|
function makeFieldFunctionOptions(policies, objectOrReference, fieldSpec, context, storage) { |
|
var storeFieldName = policies.getStoreFieldName(fieldSpec); |
|
var fieldName = fieldNameFromStoreName(storeFieldName); |
|
var variables = fieldSpec.variables || context.variables; |
|
var _a = context.store, toReference = _a.toReference, canRead = _a.canRead; |
|
return { |
|
args: argsFromFieldSpecifier(fieldSpec), |
|
field: fieldSpec.field || null, |
|
fieldName: fieldName, |
|
storeFieldName: storeFieldName, |
|
variables: variables, |
|
isReference: isReference, |
|
toReference: toReference, |
|
storage: storage, |
|
cache: policies.cache, |
|
canRead: canRead, |
|
readField: function () { |
|
return policies.readField(normalizeReadFieldOptions(arguments, objectOrReference, variables), context); |
|
}, |
|
mergeObjects: makeMergeObjectsFunction(context.store), |
|
}; |
|
} |
|
export function normalizeReadFieldOptions(readFieldArgs, objectOrReference, variables) { |
|
var fieldNameOrOptions = readFieldArgs[0], from = readFieldArgs[1], argc = readFieldArgs.length; |
|
var options; |
|
if (typeof fieldNameOrOptions === "string") { |
|
options = { |
|
fieldName: fieldNameOrOptions, |
|
// Default to objectOrReference only when no second argument was |
|
// passed for the from parameter, not when undefined is explicitly |
|
// passed as the second argument. |
|
from: argc > 1 ? from : objectOrReference, |
|
}; |
|
} |
|
else { |
|
options = __assign({}, fieldNameOrOptions); |
|
// Default to objectOrReference only when fieldNameOrOptions.from is |
|
// actually omitted, rather than just undefined. |
|
if (!hasOwn.call(options, "from")) { |
|
options.from = objectOrReference; |
|
} |
|
} |
|
if (globalThis.__DEV__ !== false && options.from === void 0) { |
|
globalThis.__DEV__ !== false && invariant.warn(7, stringifyForDisplay(Array.from(readFieldArgs))); |
|
} |
|
if (void 0 === options.variables) { |
|
options.variables = variables; |
|
} |
|
return options; |
|
} |
|
function makeMergeObjectsFunction(store) { |
|
return function mergeObjects(existing, incoming) { |
|
if (isArray(existing) || isArray(incoming)) { |
|
throw newInvariantError(8); |
|
} |
|
// These dynamic checks are necessary because the parameters of a |
|
// custom merge function can easily have the any type, so the type |
|
// system cannot always enforce the StoreObject | Reference parameter |
|
// types of options.mergeObjects. |
|
if (isNonNullObject(existing) && isNonNullObject(incoming)) { |
|
var eType = store.getFieldValue(existing, "__typename"); |
|
var iType = store.getFieldValue(incoming, "__typename"); |
|
var typesDiffer = eType && iType && eType !== iType; |
|
if (typesDiffer) { |
|
return incoming; |
|
} |
|
if (isReference(existing) && storeValueIsStoreObject(incoming)) { |
|
// Update the normalized EntityStore for the entity identified by |
|
// existing.__ref, preferring/overwriting any fields contributed by the |
|
// newer incoming StoreObject. |
|
store.merge(existing.__ref, incoming); |
|
return existing; |
|
} |
|
if (storeValueIsStoreObject(existing) && isReference(incoming)) { |
|
// Update the normalized EntityStore for the entity identified by |
|
// incoming.__ref, taking fields from the older existing object only if |
|
// those fields are not already present in the newer StoreObject |
|
// identified by incoming.__ref. |
|
store.merge(existing, incoming.__ref); |
|
return incoming; |
|
} |
|
if (storeValueIsStoreObject(existing) && |
|
storeValueIsStoreObject(incoming)) { |
|
return __assign(__assign({}, existing), incoming); |
|
} |
|
} |
|
return incoming; |
|
}; |
|
} |
|
//# sourceMappingURL=policies.js.map
|