UNPKG

@apollo/client

Version:

A fully-featured caching GraphQL client.

394 lines 19 kB
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