UNPKG

19 kBJavaScriptView Raw
1import { __assign } from "tslib";
2import { invariant, InvariantError } from "../../utilities/globals/index.js";
3import { equal } from '@wry/equality';
4import { Trie } from '@wry/trie';
5import { Kind, } from 'graphql';
6import { getFragmentFromSelection, getDefaultValues, getOperationDefinition, getTypenameFromResult, makeReference, isField, resultKeyNameFromField, isReference, shouldInclude, cloneDeep, addTypenameToDocument, isNonEmptyArray, argumentsObjectFromField, } from "../../utilities/index.js";
7import { isArray, makeProcessedFieldsMerger, fieldNameFromStoreName, storeValueIsStoreObject, extractFragmentContext } from "./helpers.js";
8import { canonicalStringify } from "./object-canon.js";
9import { normalizeReadFieldOptions } from "./policies.js";
10;
11function getContextFlavor(context, clientOnly, deferred) {
12 var key = "".concat(clientOnly).concat(deferred);
13 var flavored = context.flavors.get(key);
14 if (!flavored) {
15 context.flavors.set(key, flavored = (context.clientOnly === clientOnly &&
16 context.deferred === deferred) ? context : __assign(__assign({}, context), { clientOnly: clientOnly, deferred: deferred }));
17 }
18 return flavored;
19}
20var StoreWriter = (function () {
21 function StoreWriter(cache, reader, fragments) {
22 this.cache = cache;
23 this.reader = reader;
24 this.fragments = fragments;
25 }
26 StoreWriter.prototype.writeToStore = function (store, _a) {
27 var _this = this;
28 var query = _a.query, result = _a.result, dataId = _a.dataId, variables = _a.variables, overwrite = _a.overwrite;
29 var operationDefinition = getOperationDefinition(query);
30 var merger = makeProcessedFieldsMerger();
31 variables = __assign(__assign({}, getDefaultValues(operationDefinition)), variables);
32 var context = __assign(__assign({ store: store, written: Object.create(null), merge: function (existing, incoming) {
33 return merger.merge(existing, incoming);
34 }, variables: variables, varString: canonicalStringify(variables) }, extractFragmentContext(query, this.fragments)), { overwrite: !!overwrite, incomingById: new Map, clientOnly: false, deferred: false, flavors: new Map });
35 var ref = this.processSelectionSet({
36 result: result || Object.create(null),
37 dataId: dataId,
38 selectionSet: operationDefinition.selectionSet,
39 mergeTree: { map: new Map },
40 context: context,
41 });
42 if (!isReference(ref)) {
43 throw __DEV__ ? new InvariantError("Could not identify object ".concat(JSON.stringify(result))) : new InvariantError(7);
44 }
45 context.incomingById.forEach(function (_a, dataId) {
46 var storeObject = _a.storeObject, mergeTree = _a.mergeTree, fieldNodeSet = _a.fieldNodeSet;
47 var entityRef = makeReference(dataId);
48 if (mergeTree && mergeTree.map.size) {
49 var applied = _this.applyMerges(mergeTree, entityRef, storeObject, context);
50 if (isReference(applied)) {
51 return;
52 }
53 storeObject = applied;
54 }
55 if (__DEV__ && !context.overwrite) {
56 var fieldsWithSelectionSets_1 = Object.create(null);
57 fieldNodeSet.forEach(function (field) {
58 if (field.selectionSet) {
59 fieldsWithSelectionSets_1[field.name.value] = true;
60 }
61 });
62 var hasSelectionSet_1 = function (storeFieldName) {
63 return fieldsWithSelectionSets_1[fieldNameFromStoreName(storeFieldName)] === true;
64 };
65 var hasMergeFunction_1 = function (storeFieldName) {
66 var childTree = mergeTree && mergeTree.map.get(storeFieldName);
67 return Boolean(childTree && childTree.info && childTree.info.merge);
68 };
69 Object.keys(storeObject).forEach(function (storeFieldName) {
70 if (hasSelectionSet_1(storeFieldName) &&
71 !hasMergeFunction_1(storeFieldName)) {
72 warnAboutDataLoss(entityRef, storeObject, storeFieldName, context.store);
73 }
74 });
75 }
76 store.merge(dataId, storeObject);
77 });
78 store.retain(ref.__ref);
79 return ref;
80 };
81 StoreWriter.prototype.processSelectionSet = function (_a) {
82 var _this = this;
83 var dataId = _a.dataId, result = _a.result, selectionSet = _a.selectionSet, context = _a.context, mergeTree = _a.mergeTree;
84 var policies = this.cache.policies;
85 var incoming = Object.create(null);
86 var typename = (dataId && policies.rootTypenamesById[dataId]) ||
87 getTypenameFromResult(result, selectionSet, context.fragmentMap) ||
88 (dataId && context.store.get(dataId, "__typename"));
89 if ("string" === typeof typename) {
90 incoming.__typename = typename;
91 }
92 var readField = function () {
93 var options = normalizeReadFieldOptions(arguments, incoming, context.variables);
94 if (isReference(options.from)) {
95 var info = context.incomingById.get(options.from.__ref);
96 if (info) {
97 var result_1 = policies.readField(__assign(__assign({}, options), { from: info.storeObject }), context);
98 if (result_1 !== void 0) {
99 return result_1;
100 }
101 }
102 }
103 return policies.readField(options, context);
104 };
105 var fieldNodeSet = new Set();
106 this.flattenFields(selectionSet, result, context, typename).forEach(function (context, field) {
107 var _a;
108 var resultFieldKey = resultKeyNameFromField(field);
109 var value = result[resultFieldKey];
110 fieldNodeSet.add(field);
111 if (value !== void 0) {
112 var storeFieldName = policies.getStoreFieldName({
113 typename: typename,
114 fieldName: field.name.value,
115 field: field,
116 variables: context.variables,
117 });
118 var childTree = getChildMergeTree(mergeTree, storeFieldName);
119 var incomingValue = _this.processFieldValue(value, field, field.selectionSet
120 ? getContextFlavor(context, false, false)
121 : context, childTree);
122 var childTypename = void 0;
123 if (field.selectionSet &&
124 (isReference(incomingValue) ||
125 storeValueIsStoreObject(incomingValue))) {
126 childTypename = readField("__typename", incomingValue);
127 }
128 var merge = policies.getMergeFunction(typename, field.name.value, childTypename);
129 if (merge) {
130 childTree.info = {
131 field: field,
132 typename: typename,
133 merge: merge,
134 };
135 }
136 else {
137 maybeRecycleChildMergeTree(mergeTree, storeFieldName);
138 }
139 incoming = context.merge(incoming, (_a = {},
140 _a[storeFieldName] = incomingValue,
141 _a));
142 }
143 else if (__DEV__ &&
144 !context.clientOnly &&
145 !context.deferred &&
146 !addTypenameToDocument.added(field) &&
147 !policies.getReadFunction(typename, field.name.value)) {
148 __DEV__ && invariant.error("Missing field '".concat(resultKeyNameFromField(field), "' while writing result ").concat(JSON.stringify(result, null, 2)).substring(0, 1000));
149 }
150 });
151 try {
152 var _b = policies.identify(result, {
153 typename: typename,
154 selectionSet: selectionSet,
155 fragmentMap: context.fragmentMap,
156 storeObject: incoming,
157 readField: readField,
158 }), id = _b[0], keyObject = _b[1];
159 dataId = dataId || id;
160 if (keyObject) {
161 incoming = context.merge(incoming, keyObject);
162 }
163 }
164 catch (e) {
165 if (!dataId)
166 throw e;
167 }
168 if ("string" === typeof dataId) {
169 var dataRef = makeReference(dataId);
170 var sets = context.written[dataId] || (context.written[dataId] = []);
171 if (sets.indexOf(selectionSet) >= 0)
172 return dataRef;
173 sets.push(selectionSet);
174 if (this.reader && this.reader.isFresh(result, dataRef, selectionSet, context)) {
175 return dataRef;
176 }
177 var previous_1 = context.incomingById.get(dataId);
178 if (previous_1) {
179 previous_1.storeObject = context.merge(previous_1.storeObject, incoming);
180 previous_1.mergeTree = mergeMergeTrees(previous_1.mergeTree, mergeTree);
181 fieldNodeSet.forEach(function (field) { return previous_1.fieldNodeSet.add(field); });
182 }
183 else {
184 context.incomingById.set(dataId, {
185 storeObject: incoming,
186 mergeTree: mergeTreeIsEmpty(mergeTree) ? void 0 : mergeTree,
187 fieldNodeSet: fieldNodeSet,
188 });
189 }
190 return dataRef;
191 }
192 return incoming;
193 };
194 StoreWriter.prototype.processFieldValue = function (value, field, context, mergeTree) {
195 var _this = this;
196 if (!field.selectionSet || value === null) {
197 return __DEV__ ? cloneDeep(value) : value;
198 }
199 if (isArray(value)) {
200 return value.map(function (item, i) {
201 var value = _this.processFieldValue(item, field, context, getChildMergeTree(mergeTree, i));
202 maybeRecycleChildMergeTree(mergeTree, i);
203 return value;
204 });
205 }
206 return this.processSelectionSet({
207 result: value,
208 selectionSet: field.selectionSet,
209 context: context,
210 mergeTree: mergeTree,
211 });
212 };
213 StoreWriter.prototype.flattenFields = function (selectionSet, result, context, typename) {
214 if (typename === void 0) { typename = getTypenameFromResult(result, selectionSet, context.fragmentMap); }
215 var fieldMap = new Map();
216 var policies = this.cache.policies;
217 var limitingTrie = new Trie(false);
218 (function flatten(selectionSet, inheritedContext) {
219 var visitedNode = limitingTrie.lookup(selectionSet, inheritedContext.clientOnly, inheritedContext.deferred);
220 if (visitedNode.visited)
221 return;
222 visitedNode.visited = true;
223 selectionSet.selections.forEach(function (selection) {
224 if (!shouldInclude(selection, context.variables))
225 return;
226 var clientOnly = inheritedContext.clientOnly, deferred = inheritedContext.deferred;
227 if (!(clientOnly && deferred) &&
228 isNonEmptyArray(selection.directives)) {
229 selection.directives.forEach(function (dir) {
230 var name = dir.name.value;
231 if (name === "client")
232 clientOnly = true;
233 if (name === "defer") {
234 var args = argumentsObjectFromField(dir, context.variables);
235 if (!args || args.if !== false) {
236 deferred = true;
237 }
238 }
239 });
240 }
241 if (isField(selection)) {
242 var existing = fieldMap.get(selection);
243 if (existing) {
244 clientOnly = clientOnly && existing.clientOnly;
245 deferred = deferred && existing.deferred;
246 }
247 fieldMap.set(selection, getContextFlavor(context, clientOnly, deferred));
248 }
249 else {
250 var fragment = getFragmentFromSelection(selection, context.lookupFragment);
251 if (!fragment && selection.kind === Kind.FRAGMENT_SPREAD) {
252 throw __DEV__ ? new InvariantError("No fragment named ".concat(selection.name.value)) : new InvariantError(8);
253 }
254 if (fragment &&
255 policies.fragmentMatches(fragment, typename, result, context.variables)) {
256 flatten(fragment.selectionSet, getContextFlavor(context, clientOnly, deferred));
257 }
258 }
259 });
260 })(selectionSet, context);
261 return fieldMap;
262 };
263 StoreWriter.prototype.applyMerges = function (mergeTree, existing, incoming, context, getStorageArgs) {
264 var _a;
265 var _this = this;
266 if (mergeTree.map.size && !isReference(incoming)) {
267 var e_1 = (!isArray(incoming) &&
268 (isReference(existing) || storeValueIsStoreObject(existing))) ? existing : void 0;
269 var i_1 = incoming;
270 if (e_1 && !getStorageArgs) {
271 getStorageArgs = [isReference(e_1) ? e_1.__ref : e_1];
272 }
273 var changedFields_1;
274 var getValue_1 = function (from, name) {
275 return isArray(from)
276 ? (typeof name === "number" ? from[name] : void 0)
277 : context.store.getFieldValue(from, String(name));
278 };
279 mergeTree.map.forEach(function (childTree, storeFieldName) {
280 var eVal = getValue_1(e_1, storeFieldName);
281 var iVal = getValue_1(i_1, storeFieldName);
282 if (void 0 === iVal)
283 return;
284 if (getStorageArgs) {
285 getStorageArgs.push(storeFieldName);
286 }
287 var aVal = _this.applyMerges(childTree, eVal, iVal, context, getStorageArgs);
288 if (aVal !== iVal) {
289 changedFields_1 = changedFields_1 || new Map;
290 changedFields_1.set(storeFieldName, aVal);
291 }
292 if (getStorageArgs) {
293 invariant(getStorageArgs.pop() === storeFieldName);
294 }
295 });
296 if (changedFields_1) {
297 incoming = (isArray(i_1) ? i_1.slice(0) : __assign({}, i_1));
298 changedFields_1.forEach(function (value, name) {
299 incoming[name] = value;
300 });
301 }
302 }
303 if (mergeTree.info) {
304 return this.cache.policies.runMergeFunction(existing, incoming, mergeTree.info, context, getStorageArgs && (_a = context.store).getStorage.apply(_a, getStorageArgs));
305 }
306 return incoming;
307 };
308 return StoreWriter;
309}());
310export { StoreWriter };
311var emptyMergeTreePool = [];
312function getChildMergeTree(_a, name) {
313 var map = _a.map;
314 if (!map.has(name)) {
315 map.set(name, emptyMergeTreePool.pop() || { map: new Map });
316 }
317 return map.get(name);
318}
319function mergeMergeTrees(left, right) {
320 if (left === right || !right || mergeTreeIsEmpty(right))
321 return left;
322 if (!left || mergeTreeIsEmpty(left))
323 return right;
324 var info = left.info && right.info ? __assign(__assign({}, left.info), right.info) : left.info || right.info;
325 var needToMergeMaps = left.map.size && right.map.size;
326 var map = needToMergeMaps ? new Map :
327 left.map.size ? left.map : right.map;
328 var merged = { info: info, map: map };
329 if (needToMergeMaps) {
330 var remainingRightKeys_1 = new Set(right.map.keys());
331 left.map.forEach(function (leftTree, key) {
332 merged.map.set(key, mergeMergeTrees(leftTree, right.map.get(key)));
333 remainingRightKeys_1.delete(key);
334 });
335 remainingRightKeys_1.forEach(function (key) {
336 merged.map.set(key, mergeMergeTrees(right.map.get(key), left.map.get(key)));
337 });
338 }
339 return merged;
340}
341function mergeTreeIsEmpty(tree) {
342 return !tree || !(tree.info || tree.map.size);
343}
344function maybeRecycleChildMergeTree(_a, name) {
345 var map = _a.map;
346 var childTree = map.get(name);
347 if (childTree && mergeTreeIsEmpty(childTree)) {
348 emptyMergeTreePool.push(childTree);
349 map.delete(name);
350 }
351}
352var warnings = new Set();
353function warnAboutDataLoss(existingRef, incomingObj, storeFieldName, store) {
354 var getChild = function (objOrRef) {
355 var child = store.getFieldValue(objOrRef, storeFieldName);
356 return typeof child === "object" && child;
357 };
358 var existing = getChild(existingRef);
359 if (!existing)
360 return;
361 var incoming = getChild(incomingObj);
362 if (!incoming)
363 return;
364 if (isReference(existing))
365 return;
366 if (equal(existing, incoming))
367 return;
368 if (Object.keys(existing).every(function (key) { return store.getFieldValue(incoming, key) !== void 0; })) {
369 return;
370 }
371 var parentType = store.getFieldValue(existingRef, "__typename") ||
372 store.getFieldValue(incomingObj, "__typename");
373 var fieldName = fieldNameFromStoreName(storeFieldName);
374 var typeDotName = "".concat(parentType, ".").concat(fieldName);
375 if (warnings.has(typeDotName))
376 return;
377 warnings.add(typeDotName);
378 var childTypenames = [];
379 if (!isArray(existing) &&
380 !isArray(incoming)) {
381 [existing, incoming].forEach(function (child) {
382 var typename = store.getFieldValue(child, "__typename");
383 if (typeof typename === "string" &&
384 !childTypenames.includes(typename)) {
385 childTypenames.push(typename);
386 }
387 });
388 }
389 __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
390 ? "either ensure all objects of type " +
391 childTypenames.join(" and ") + " have an ID or a custom merge function, or "
392 : "", "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"));
393}
394//# sourceMappingURL=writeToStore.js.map
\No newline at end of file