UNPKG

16 kBJavaScriptView Raw
1import { __assign } from "tslib";
2import { invariant, newInvariantError } from "../../utilities/globals/index.js";
3import { Kind } from "graphql";
4import { wrap } from "optimism";
5import { isField, resultKeyNameFromField, isReference, makeReference, shouldInclude, addTypenameToDocument, getDefaultValues, getMainDefinition, getQueryDefinition, getFragmentFromSelection, maybeDeepFreeze, mergeDeepArray, DeepMerger, isNonNullObject, canUseWeakMap, compact, canonicalStringify, cacheSizes, } from "../../utilities/index.js";
6import { maybeDependOnExistenceOfEntity, supportsResultCaching, } from "./entityStore.js";
7import { isArray, extractFragmentContext, getTypenameFromStoreObject, shouldCanonizeResults, } from "./helpers.js";
8import { MissingFieldError } from "../core/types/common.js";
9import { ObjectCanon } from "./object-canon.js";
10function execSelectionSetKeyArgs(options) {
11 return [
12 options.selectionSet,
13 options.objectOrReference,
14 options.context,
15 // We split out this property so we can pass different values
16 // independently without modifying options.context itself.
17 options.context.canonizeResults,
18 ];
19}
20var StoreReader = /** @class */ (function () {
21 function StoreReader(config) {
22 var _this = this;
23 this.knownResults = new (canUseWeakMap ? WeakMap : Map)();
24 this.config = compact(config, {
25 addTypename: config.addTypename !== false,
26 canonizeResults: shouldCanonizeResults(config),
27 });
28 this.canon = config.canon || new ObjectCanon();
29 // memoized functions in this class will be "garbage-collected"
30 // by recreating the whole `StoreReader` in
31 // `InMemoryCache.resetResultsCache`
32 // (triggered from `InMemoryCache.gc` with `resetResultCache: true`)
33 this.executeSelectionSet = wrap(function (options) {
34 var _a;
35 var canonizeResults = options.context.canonizeResults;
36 var peekArgs = execSelectionSetKeyArgs(options);
37 // Negate this boolean option so we can find out if we've already read
38 // this result using the other boolean value.
39 peekArgs[3] = !canonizeResults;
40 var other = (_a = _this.executeSelectionSet).peek.apply(_a, peekArgs);
41 if (other) {
42 if (canonizeResults) {
43 return __assign(__assign({}, other), {
44 // If we previously read this result without canonizing it, we can
45 // reuse that result simply by canonizing it now.
46 result: _this.canon.admit(other.result) });
47 }
48 // If we previously read this result with canonization enabled, we can
49 // return that canonized result as-is.
50 return other;
51 }
52 maybeDependOnExistenceOfEntity(options.context.store, options.enclosingRef.__ref);
53 // Finally, if we didn't find any useful previous results, run the real
54 // execSelectionSetImpl method with the given options.
55 return _this.execSelectionSetImpl(options);
56 }, {
57 max: this.config.resultCacheMaxSize ||
58 cacheSizes["inMemoryCache.executeSelectionSet"] ||
59 50000 /* defaultCacheSizes["inMemoryCache.executeSelectionSet"] */,
60 keyArgs: execSelectionSetKeyArgs,
61 // Note that the parameters of makeCacheKey are determined by the
62 // array returned by keyArgs.
63 makeCacheKey: function (selectionSet, parent, context, canonizeResults) {
64 if (supportsResultCaching(context.store)) {
65 return context.store.makeCacheKey(selectionSet, isReference(parent) ? parent.__ref : parent, context.varString, canonizeResults);
66 }
67 },
68 });
69 this.executeSubSelectedArray = wrap(function (options) {
70 maybeDependOnExistenceOfEntity(options.context.store, options.enclosingRef.__ref);
71 return _this.execSubSelectedArrayImpl(options);
72 }, {
73 max: this.config.resultCacheMaxSize ||
74 cacheSizes["inMemoryCache.executeSubSelectedArray"] ||
75 10000 /* defaultCacheSizes["inMemoryCache.executeSubSelectedArray"] */,
76 makeCacheKey: function (_a) {
77 var field = _a.field, array = _a.array, context = _a.context;
78 if (supportsResultCaching(context.store)) {
79 return context.store.makeCacheKey(field, array, context.varString);
80 }
81 },
82 });
83 }
84 StoreReader.prototype.resetCanon = function () {
85 this.canon = new ObjectCanon();
86 };
87 /**
88 * Given a store and a query, return as much of the result as possible and
89 * identify if any data was missing from the store.
90 */
91 StoreReader.prototype.diffQueryAgainstStore = function (_a) {
92 var store = _a.store, query = _a.query, _b = _a.rootId, rootId = _b === void 0 ? "ROOT_QUERY" : _b, variables = _a.variables, _c = _a.returnPartialData, returnPartialData = _c === void 0 ? true : _c, _d = _a.canonizeResults, canonizeResults = _d === void 0 ? this.config.canonizeResults : _d;
93 var policies = this.config.cache.policies;
94 variables = __assign(__assign({}, getDefaultValues(getQueryDefinition(query))), variables);
95 var rootRef = makeReference(rootId);
96 var execResult = this.executeSelectionSet({
97 selectionSet: getMainDefinition(query).selectionSet,
98 objectOrReference: rootRef,
99 enclosingRef: rootRef,
100 context: __assign({ store: store, query: query, policies: policies, variables: variables, varString: canonicalStringify(variables), canonizeResults: canonizeResults }, extractFragmentContext(query, this.config.fragments)),
101 });
102 var missing;
103 if (execResult.missing) {
104 // For backwards compatibility we still report an array of
105 // MissingFieldError objects, even though there will only ever be at most
106 // one of them, now that all missing field error messages are grouped
107 // together in the execResult.missing tree.
108 missing = [
109 new MissingFieldError(firstMissing(execResult.missing), execResult.missing, query, variables),
110 ];
111 if (!returnPartialData) {
112 throw missing[0];
113 }
114 }
115 return {
116 result: execResult.result,
117 complete: !missing,
118 missing: missing,
119 };
120 };
121 StoreReader.prototype.isFresh = function (result, parent, selectionSet, context) {
122 if (supportsResultCaching(context.store) &&
123 this.knownResults.get(result) === selectionSet) {
124 var latest = this.executeSelectionSet.peek(selectionSet, parent, context,
125 // If result is canonical, then it could only have been previously
126 // cached by the canonizing version of executeSelectionSet, so we can
127 // avoid checking both possibilities here.
128 this.canon.isKnown(result));
129 if (latest && result === latest.result) {
130 return true;
131 }
132 }
133 return false;
134 };
135 // Uncached version of executeSelectionSet.
136 StoreReader.prototype.execSelectionSetImpl = function (_a) {
137 var _this = this;
138 var selectionSet = _a.selectionSet, objectOrReference = _a.objectOrReference, enclosingRef = _a.enclosingRef, context = _a.context;
139 if (isReference(objectOrReference) &&
140 !context.policies.rootTypenamesById[objectOrReference.__ref] &&
141 !context.store.has(objectOrReference.__ref)) {
142 return {
143 result: this.canon.empty,
144 missing: "Dangling reference to missing ".concat(objectOrReference.__ref, " object"),
145 };
146 }
147 var variables = context.variables, policies = context.policies, store = context.store;
148 var typename = store.getFieldValue(objectOrReference, "__typename");
149 var objectsToMerge = [];
150 var missing;
151 var missingMerger = new DeepMerger();
152 if (this.config.addTypename &&
153 typeof typename === "string" &&
154 !policies.rootIdsByTypename[typename]) {
155 // Ensure we always include a default value for the __typename
156 // field, if we have one, and this.config.addTypename is true. Note
157 // that this field can be overridden by other merged objects.
158 objectsToMerge.push({ __typename: typename });
159 }
160 function handleMissing(result, resultName) {
161 var _a;
162 if (result.missing) {
163 missing = missingMerger.merge(missing, (_a = {},
164 _a[resultName] = result.missing,
165 _a));
166 }
167 return result.result;
168 }
169 var workSet = new Set(selectionSet.selections);
170 workSet.forEach(function (selection) {
171 var _a, _b;
172 // Omit fields with directives @skip(if: <truthy value>) or
173 // @include(if: <falsy value>).
174 if (!shouldInclude(selection, variables))
175 return;
176 if (isField(selection)) {
177 var fieldValue = policies.readField({
178 fieldName: selection.name.value,
179 field: selection,
180 variables: context.variables,
181 from: objectOrReference,
182 }, context);
183 var resultName = resultKeyNameFromField(selection);
184 if (fieldValue === void 0) {
185 if (!addTypenameToDocument.added(selection)) {
186 missing = missingMerger.merge(missing, (_a = {},
187 _a[resultName] = "Can't find field '".concat(selection.name.value, "' on ").concat(isReference(objectOrReference) ?
188 objectOrReference.__ref + " object"
189 : "object " + JSON.stringify(objectOrReference, null, 2)),
190 _a));
191 }
192 }
193 else if (isArray(fieldValue)) {
194 if (fieldValue.length > 0) {
195 fieldValue = handleMissing(_this.executeSubSelectedArray({
196 field: selection,
197 array: fieldValue,
198 enclosingRef: enclosingRef,
199 context: context,
200 }), resultName);
201 }
202 }
203 else if (!selection.selectionSet) {
204 // If the field does not have a selection set, then we handle it
205 // as a scalar value. To keep this.canon from canonicalizing
206 // this value, we use this.canon.pass to wrap fieldValue in a
207 // Pass object that this.canon.admit will later unwrap as-is.
208 if (context.canonizeResults) {
209 fieldValue = _this.canon.pass(fieldValue);
210 }
211 }
212 else if (fieldValue != null) {
213 // In this case, because we know the field has a selection set,
214 // it must be trying to query a GraphQLObjectType, which is why
215 // fieldValue must be != null.
216 fieldValue = handleMissing(_this.executeSelectionSet({
217 selectionSet: selection.selectionSet,
218 objectOrReference: fieldValue,
219 enclosingRef: isReference(fieldValue) ? fieldValue : enclosingRef,
220 context: context,
221 }), resultName);
222 }
223 if (fieldValue !== void 0) {
224 objectsToMerge.push((_b = {}, _b[resultName] = fieldValue, _b));
225 }
226 }
227 else {
228 var fragment = getFragmentFromSelection(selection, context.lookupFragment);
229 if (!fragment && selection.kind === Kind.FRAGMENT_SPREAD) {
230 throw newInvariantError(9, selection.name.value);
231 }
232 if (fragment && policies.fragmentMatches(fragment, typename)) {
233 fragment.selectionSet.selections.forEach(workSet.add, workSet);
234 }
235 }
236 });
237 var result = mergeDeepArray(objectsToMerge);
238 var finalResult = { result: result, missing: missing };
239 var frozen = context.canonizeResults ?
240 this.canon.admit(finalResult)
241 // Since this.canon is normally responsible for freezing results (only in
242 // development), freeze them manually if canonization is disabled.
243 : maybeDeepFreeze(finalResult);
244 // Store this result with its selection set so that we can quickly
245 // recognize it again in the StoreReader#isFresh method.
246 if (frozen.result) {
247 this.knownResults.set(frozen.result, selectionSet);
248 }
249 return frozen;
250 };
251 // Uncached version of executeSubSelectedArray.
252 StoreReader.prototype.execSubSelectedArrayImpl = function (_a) {
253 var _this = this;
254 var field = _a.field, array = _a.array, enclosingRef = _a.enclosingRef, context = _a.context;
255 var missing;
256 var missingMerger = new DeepMerger();
257 function handleMissing(childResult, i) {
258 var _a;
259 if (childResult.missing) {
260 missing = missingMerger.merge(missing, (_a = {}, _a[i] = childResult.missing, _a));
261 }
262 return childResult.result;
263 }
264 if (field.selectionSet) {
265 array = array.filter(context.store.canRead);
266 }
267 array = array.map(function (item, i) {
268 // null value in array
269 if (item === null) {
270 return null;
271 }
272 // This is a nested array, recurse
273 if (isArray(item)) {
274 return handleMissing(_this.executeSubSelectedArray({
275 field: field,
276 array: item,
277 enclosingRef: enclosingRef,
278 context: context,
279 }), i);
280 }
281 // This is an object, run the selection set on it
282 if (field.selectionSet) {
283 return handleMissing(_this.executeSelectionSet({
284 selectionSet: field.selectionSet,
285 objectOrReference: item,
286 enclosingRef: isReference(item) ? item : enclosingRef,
287 context: context,
288 }), i);
289 }
290 if (globalThis.__DEV__ !== false) {
291 assertSelectionSetForIdValue(context.store, field, item);
292 }
293 return item;
294 });
295 return {
296 result: context.canonizeResults ? this.canon.admit(array) : array,
297 missing: missing,
298 };
299 };
300 return StoreReader;
301}());
302export { StoreReader };
303function firstMissing(tree) {
304 try {
305 JSON.stringify(tree, function (_, value) {
306 if (typeof value === "string")
307 throw value;
308 return value;
309 });
310 }
311 catch (result) {
312 return result;
313 }
314}
315function assertSelectionSetForIdValue(store, field, fieldValue) {
316 if (!field.selectionSet) {
317 var workSet_1 = new Set([fieldValue]);
318 workSet_1.forEach(function (value) {
319 if (isNonNullObject(value)) {
320 invariant(
321 !isReference(value),
322 10,
323 getTypenameFromStoreObject(store, value),
324 field.name.value
325 );
326 Object.values(value).forEach(workSet_1.add, workSet_1);
327 }
328 });
329 }
330}
331//# sourceMappingURL=readFromStore.js.map
\No newline at end of file