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
4 months ago
|
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
|