1 | import { __assign } from "tslib";
|
2 | import { invariant, InvariantError } from "../../utilities/globals/index.js";
|
3 | import { equal } from '@wry/equality';
|
4 | import { Trie } from '@wry/trie';
|
5 | import { Kind, } from 'graphql';
|
6 | import { getFragmentFromSelection, getDefaultValues, getOperationDefinition, getTypenameFromResult, makeReference, isField, resultKeyNameFromField, isReference, shouldInclude, cloneDeep, addTypenameToDocument, isNonEmptyArray, argumentsObjectFromField, } from "../../utilities/index.js";
|
7 | import { isArray, makeProcessedFieldsMerger, fieldNameFromStoreName, storeValueIsStoreObject, extractFragmentContext } from "./helpers.js";
|
8 | import { canonicalStringify } from "./object-canon.js";
|
9 | import { normalizeReadFieldOptions } from "./policies.js";
|
10 | ;
|
11 | function 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 | }
|
20 | var 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 | }());
|
310 | export { StoreWriter };
|
311 | var emptyMergeTreePool = [];
|
312 | function 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 | }
|
319 | function 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 | }
|
341 | function mergeTreeIsEmpty(tree) {
|
342 | return !tree || !(tree.info || tree.map.size);
|
343 | }
|
344 | function 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 | }
|
352 | var warnings = new Set();
|
353 | function 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 |
|
\ | No newline at end of file |