@apollo/client
Version:
A fully-featured caching GraphQL client.
394 lines • 19 kB
JavaScript
import { __assign } from "tslib";
import { invariant, InvariantError } from "../../utilities/globals/index.js";
import { equal } from '@wry/equality';
import { Trie } from '@wry/trie';
import { Kind, } from 'graphql';
import { getFragmentFromSelection, getDefaultValues, getOperationDefinition, getTypenameFromResult, makeReference, isField, resultKeyNameFromField, isReference, shouldInclude, cloneDeep, addTypenameToDocument, isNonEmptyArray, argumentsObjectFromField, } from "../../utilities/index.js";
import { isArray, makeProcessedFieldsMerger, fieldNameFromStoreName, storeValueIsStoreObject, extractFragmentContext } from "./helpers.js";
import { canonicalStringify } from "./object-canon.js";
import { normalizeReadFieldOptions } from "./policies.js";
;
function getContextFlavor(context, clientOnly, deferred) {
var key = "".concat(clientOnly).concat(deferred);
var flavored = context.flavors.get(key);
if (!flavored) {
context.flavors.set(key, flavored = (context.clientOnly === clientOnly &&
context.deferred === deferred) ? context : __assign(__assign({}, context), { clientOnly: clientOnly, deferred: deferred }));
}
return flavored;
}
var StoreWriter = (function () {
function StoreWriter(cache, reader, fragments) {
this.cache = cache;
this.reader = reader;
this.fragments = fragments;
}
StoreWriter.prototype.writeToStore = function (store, _a) {
var _this = this;
var query = _a.query, result = _a.result, dataId = _a.dataId, variables = _a.variables, overwrite = _a.overwrite;
var operationDefinition = getOperationDefinition(query);
var merger = makeProcessedFieldsMerger();
variables = __assign(__assign({}, getDefaultValues(operationDefinition)), variables);
var context = __assign(__assign({ store: store, written: Object.create(null), merge: function (existing, incoming) {
return merger.merge(existing, incoming);
}, variables: variables, varString: canonicalStringify(variables) }, extractFragmentContext(query, this.fragments)), { overwrite: !!overwrite, incomingById: new Map, clientOnly: false, deferred: false, flavors: new Map });
var ref = this.processSelectionSet({
result: result || Object.create(null),
dataId: dataId,
selectionSet: operationDefinition.selectionSet,
mergeTree: { map: new Map },
context: context,
});
if (!isReference(ref)) {
throw __DEV__ ? new InvariantError("Could not identify object ".concat(JSON.stringify(result))) : new InvariantError(7);
}
context.incomingById.forEach(function (_a, dataId) {
var storeObject = _a.storeObject, mergeTree = _a.mergeTree, fieldNodeSet = _a.fieldNodeSet;
var entityRef = makeReference(dataId);
if (mergeTree && mergeTree.map.size) {
var applied = _this.applyMerges(mergeTree, entityRef, storeObject, context);
if (isReference(applied)) {
return;
}
storeObject = applied;
}
if (__DEV__ && !context.overwrite) {
var fieldsWithSelectionSets_1 = Object.create(null);
fieldNodeSet.forEach(function (field) {
if (field.selectionSet) {
fieldsWithSelectionSets_1[field.name.value] = true;
}
});
var hasSelectionSet_1 = function (storeFieldName) {
return fieldsWithSelectionSets_1[fieldNameFromStoreName(storeFieldName)] === true;
};
var hasMergeFunction_1 = function (storeFieldName) {
var childTree = mergeTree && mergeTree.map.get(storeFieldName);
return Boolean(childTree && childTree.info && childTree.info.merge);
};
Object.keys(storeObject).forEach(function (storeFieldName) {
if (hasSelectionSet_1(storeFieldName) &&
!hasMergeFunction_1(storeFieldName)) {
warnAboutDataLoss(entityRef, storeObject, storeFieldName, context.store);
}
});
}
store.merge(dataId, storeObject);
});
store.retain(ref.__ref);
return ref;
};
StoreWriter.prototype.processSelectionSet = function (_a) {
var _this = this;
var dataId = _a.dataId, result = _a.result, selectionSet = _a.selectionSet, context = _a.context, mergeTree = _a.mergeTree;
var policies = this.cache.policies;
var incoming = Object.create(null);
var typename = (dataId && policies.rootTypenamesById[dataId]) ||
getTypenameFromResult(result, selectionSet, context.fragmentMap) ||
(dataId && context.store.get(dataId, "__typename"));
if ("string" === typeof typename) {
incoming.__typename = typename;
}
var readField = function () {
var options = normalizeReadFieldOptions(arguments, incoming, context.variables);
if (isReference(options.from)) {
var info = context.incomingById.get(options.from.__ref);
if (info) {
var result_1 = policies.readField(__assign(__assign({}, options), { from: info.storeObject }), context);
if (result_1 !== void 0) {
return result_1;
}
}
}
return policies.readField(options, context);
};
var fieldNodeSet = new Set();
this.flattenFields(selectionSet, result, context, typename).forEach(function (context, field) {
var _a;
var resultFieldKey = resultKeyNameFromField(field);
var value = result[resultFieldKey];
fieldNodeSet.add(field);
if (value !== void 0) {
var storeFieldName = policies.getStoreFieldName({
typename: typename,
fieldName: field.name.value,
field: field,
variables: context.variables,
});
var childTree = getChildMergeTree(mergeTree, storeFieldName);
var incomingValue = _this.processFieldValue(value, field, field.selectionSet
? getContextFlavor(context, false, false)
: context, childTree);
var childTypename = void 0;
if (field.selectionSet &&
(isReference(incomingValue) ||
storeValueIsStoreObject(incomingValue))) {
childTypename = readField("__typename", incomingValue);
}
var merge = policies.getMergeFunction(typename, field.name.value, childTypename);
if (merge) {
childTree.info = {
field: field,
typename: typename,
merge: merge,
};
}
else {
maybeRecycleChildMergeTree(mergeTree, storeFieldName);
}
incoming = context.merge(incoming, (_a = {},
_a[storeFieldName] = incomingValue,
_a));
}
else if (__DEV__ &&
!context.clientOnly &&
!context.deferred &&
!addTypenameToDocument.added(field) &&
!policies.getReadFunction(typename, field.name.value)) {
__DEV__ && invariant.error("Missing field '".concat(resultKeyNameFromField(field), "' while writing result ").concat(JSON.stringify(result, null, 2)).substring(0, 1000));
}
});
try {
var _b = policies.identify(result, {
typename: typename,
selectionSet: selectionSet,
fragmentMap: context.fragmentMap,
storeObject: incoming,
readField: readField,
}), id = _b[0], keyObject = _b[1];
dataId = dataId || id;
if (keyObject) {
incoming = context.merge(incoming, keyObject);
}
}
catch (e) {
if (!dataId)
throw e;
}
if ("string" === typeof dataId) {
var dataRef = makeReference(dataId);
var sets = context.written[dataId] || (context.written[dataId] = []);
if (sets.indexOf(selectionSet) >= 0)
return dataRef;
sets.push(selectionSet);
if (this.reader && this.reader.isFresh(result, dataRef, selectionSet, context)) {
return dataRef;
}
var previous_1 = context.incomingById.get(dataId);
if (previous_1) {
previous_1.storeObject = context.merge(previous_1.storeObject, incoming);
previous_1.mergeTree = mergeMergeTrees(previous_1.mergeTree, mergeTree);
fieldNodeSet.forEach(function (field) { return previous_1.fieldNodeSet.add(field); });
}
else {
context.incomingById.set(dataId, {
storeObject: incoming,
mergeTree: mergeTreeIsEmpty(mergeTree) ? void 0 : mergeTree,
fieldNodeSet: fieldNodeSet,
});
}
return dataRef;
}
return incoming;
};
StoreWriter.prototype.processFieldValue = function (value, field, context, mergeTree) {
var _this = this;
if (!field.selectionSet || value === null) {
return __DEV__ ? cloneDeep(value) : value;
}
if (isArray(value)) {
return value.map(function (item, i) {
var value = _this.processFieldValue(item, field, context, getChildMergeTree(mergeTree, i));
maybeRecycleChildMergeTree(mergeTree, i);
return value;
});
}
return this.processSelectionSet({
result: value,
selectionSet: field.selectionSet,
context: context,
mergeTree: mergeTree,
});
};
StoreWriter.prototype.flattenFields = function (selectionSet, result, context, typename) {
if (typename === void 0) { typename = getTypenameFromResult(result, selectionSet, context.fragmentMap); }
var fieldMap = new Map();
var policies = this.cache.policies;
var limitingTrie = new Trie(false);
(function flatten(selectionSet, inheritedContext) {
var visitedNode = limitingTrie.lookup(selectionSet, inheritedContext.clientOnly, inheritedContext.deferred);
if (visitedNode.visited)
return;
visitedNode.visited = true;
selectionSet.selections.forEach(function (selection) {
if (!shouldInclude(selection, context.variables))
return;
var clientOnly = inheritedContext.clientOnly, deferred = inheritedContext.deferred;
if (!(clientOnly && deferred) &&
isNonEmptyArray(selection.directives)) {
selection.directives.forEach(function (dir) {
var name = dir.name.value;
if (name === "client")
clientOnly = true;
if (name === "defer") {
var args = argumentsObjectFromField(dir, context.variables);
if (!args || args.if !== false) {
deferred = true;
}
}
});
}
if (isField(selection)) {
var existing = fieldMap.get(selection);
if (existing) {
clientOnly = clientOnly && existing.clientOnly;
deferred = deferred && existing.deferred;
}
fieldMap.set(selection, getContextFlavor(context, clientOnly, deferred));
}
else {
var fragment = getFragmentFromSelection(selection, context.lookupFragment);
if (!fragment && selection.kind === Kind.FRAGMENT_SPREAD) {
throw __DEV__ ? new InvariantError("No fragment named ".concat(selection.name.value)) : new InvariantError(8);
}
if (fragment &&
policies.fragmentMatches(fragment, typename, result, context.variables)) {
flatten(fragment.selectionSet, getContextFlavor(context, clientOnly, deferred));
}
}
});
})(selectionSet, context);
return fieldMap;
};
StoreWriter.prototype.applyMerges = function (mergeTree, existing, incoming, context, getStorageArgs) {
var _a;
var _this = this;
if (mergeTree.map.size && !isReference(incoming)) {
var e_1 = (!isArray(incoming) &&
(isReference(existing) || storeValueIsStoreObject(existing))) ? existing : void 0;
var i_1 = incoming;
if (e_1 && !getStorageArgs) {
getStorageArgs = [isReference(e_1) ? e_1.__ref : e_1];
}
var changedFields_1;
var getValue_1 = function (from, name) {
return isArray(from)
? (typeof name === "number" ? from[name] : void 0)
: context.store.getFieldValue(from, String(name));
};
mergeTree.map.forEach(function (childTree, storeFieldName) {
var eVal = getValue_1(e_1, storeFieldName);
var iVal = getValue_1(i_1, storeFieldName);
if (void 0 === iVal)
return;
if (getStorageArgs) {
getStorageArgs.push(storeFieldName);
}
var aVal = _this.applyMerges(childTree, eVal, iVal, context, getStorageArgs);
if (aVal !== iVal) {
changedFields_1 = changedFields_1 || new Map;
changedFields_1.set(storeFieldName, aVal);
}
if (getStorageArgs) {
invariant(getStorageArgs.pop() === storeFieldName);
}
});
if (changedFields_1) {
incoming = (isArray(i_1) ? i_1.slice(0) : __assign({}, i_1));
changedFields_1.forEach(function (value, name) {
incoming[name] = value;
});
}
}
if (mergeTree.info) {
return this.cache.policies.runMergeFunction(existing, incoming, mergeTree.info, context, getStorageArgs && (_a = context.store).getStorage.apply(_a, getStorageArgs));
}
return incoming;
};
return StoreWriter;
}());
export { StoreWriter };
var emptyMergeTreePool = [];
function getChildMergeTree(_a, name) {
var map = _a.map;
if (!map.has(name)) {
map.set(name, emptyMergeTreePool.pop() || { map: new Map });
}
return map.get(name);
}
function mergeMergeTrees(left, right) {
if (left === right || !right || mergeTreeIsEmpty(right))
return left;
if (!left || mergeTreeIsEmpty(left))
return right;
var info = left.info && right.info ? __assign(__assign({}, left.info), right.info) : left.info || right.info;
var needToMergeMaps = left.map.size && right.map.size;
var map = needToMergeMaps ? new Map :
left.map.size ? left.map : right.map;
var merged = { info: info, map: map };
if (needToMergeMaps) {
var remainingRightKeys_1 = new Set(right.map.keys());
left.map.forEach(function (leftTree, key) {
merged.map.set(key, mergeMergeTrees(leftTree, right.map.get(key)));
remainingRightKeys_1.delete(key);
});
remainingRightKeys_1.forEach(function (key) {
merged.map.set(key, mergeMergeTrees(right.map.get(key), left.map.get(key)));
});
}
return merged;
}
function mergeTreeIsEmpty(tree) {
return !tree || !(tree.info || tree.map.size);
}
function maybeRecycleChildMergeTree(_a, name) {
var map = _a.map;
var childTree = map.get(name);
if (childTree && mergeTreeIsEmpty(childTree)) {
emptyMergeTreePool.push(childTree);
map.delete(name);
}
}
var warnings = new Set();
function warnAboutDataLoss(existingRef, incomingObj, storeFieldName, store) {
var getChild = function (objOrRef) {
var child = store.getFieldValue(objOrRef, storeFieldName);
return typeof child === "object" && child;
};
var existing = getChild(existingRef);
if (!existing)
return;
var incoming = getChild(incomingObj);
if (!incoming)
return;
if (isReference(existing))
return;
if (equal(existing, incoming))
return;
if (Object.keys(existing).every(function (key) { return store.getFieldValue(incoming, key) !== void 0; })) {
return;
}
var parentType = store.getFieldValue(existingRef, "__typename") ||
store.getFieldValue(incomingObj, "__typename");
var fieldName = fieldNameFromStoreName(storeFieldName);
var typeDotName = "".concat(parentType, ".").concat(fieldName);
if (warnings.has(typeDotName))
return;
warnings.add(typeDotName);
var childTypenames = [];
if (!isArray(existing) &&
!isArray(incoming)) {
[existing, incoming].forEach(function (child) {
var typename = store.getFieldValue(child, "__typename");
if (typeof typename === "string" &&
!childTypenames.includes(typename)) {
childTypenames.push(typename);
}
});
}
__DEV__ && invariant.warn("Cache data may be lost when replacing the ".concat(fieldName, " field of a ").concat(parentType, " object.\n\nTo address this problem (which is not a bug in Apollo Client), ").concat(childTypenames.length
? "either ensure all objects of type " +
childTypenames.join(" and ") + " have an ID or a custom merge function, or "
: "", "define a custom merge function for the ").concat(typeDotName, " field, so InMemoryCache can safely merge these objects:\n\n existing: ").concat(JSON.stringify(existing).slice(0, 1000), "\n incoming: ").concat(JSON.stringify(incoming).slice(0, 1000), "\n\nFor more information about these options, please refer to the documentation:\n\n * Ensuring entity objects have IDs: https://go.apollo.dev/c/generating-unique-identifiers\n * Defining custom merge functions: https://go.apollo.dev/c/merging-non-normalized-objects\n"));
}
//# sourceMappingURL=writeToStore.js.map