@graphql-box/cache-manager 2.1.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/lib/browser/index.js +1 -1
  2. package/lib/browser/index.js.map +1 -1
  3. package/lib/browser/production.analysis.txt +129 -17
  4. package/lib/main/debug/log-cache-entry/index.js.map +1 -1
  5. package/lib/main/debug/log-partial-compiled/index.js.map +1 -1
  6. package/lib/main/helpers/buildKeysAndPaths.js +73 -0
  7. package/lib/main/helpers/buildKeysAndPaths.js.map +1 -0
  8. package/lib/main/helpers/checkFieldPathChecklist.js +40 -0
  9. package/lib/main/helpers/checkFieldPathChecklist.js.map +1 -0
  10. package/lib/main/helpers/createFragmentSpreadChecklist.js +28 -0
  11. package/lib/main/helpers/createFragmentSpreadChecklist.js.map +1 -0
  12. package/lib/main/helpers/filterField.js +97 -0
  13. package/lib/main/helpers/filterField.js.map +1 -0
  14. package/lib/main/helpers/filterFragmentDefinitions.js +50 -0
  15. package/lib/main/helpers/filterFragmentDefinitions.js.map +1 -0
  16. package/lib/main/helpers/filterFragmentSpreads.js +37 -0
  17. package/lib/main/helpers/filterFragmentSpreads.js.map +1 -0
  18. package/lib/main/helpers/filterIDsAndTypeNames.js +47 -0
  19. package/lib/main/helpers/filterIDsAndTypeNames.js.map +1 -0
  20. package/lib/main/helpers/filterInlineFragments.js +42 -0
  21. package/lib/main/helpers/filterInlineFragments.js.map +1 -0
  22. package/lib/main/helpers/filterOutPropsWithArgsOrDirectives.js +39 -0
  23. package/lib/main/helpers/filterOutPropsWithArgsOrDirectives.js.map +1 -0
  24. package/lib/main/helpers/filterQuery.js +59 -0
  25. package/lib/main/helpers/filterQuery.js.map +1 -0
  26. package/lib/main/helpers/normalizeResponseData.js +23 -0
  27. package/lib/main/helpers/normalizeResponseData.js.map +1 -0
  28. package/lib/main/helpers/validTypeIDValue.js +20 -0
  29. package/lib/main/helpers/validTypeIDValue.js.map +1 -0
  30. package/lib/main/main/index.js +468 -472
  31. package/lib/main/main/index.js.map +1 -1
  32. package/lib/module/debug/log-cache-entry/index.js.map +1 -1
  33. package/lib/module/debug/log-partial-compiled/index.js.map +1 -1
  34. package/lib/module/helpers/buildKeysAndPaths.js +54 -0
  35. package/lib/module/helpers/buildKeysAndPaths.js.map +1 -0
  36. package/lib/module/helpers/checkFieldPathChecklist.js +31 -0
  37. package/lib/module/helpers/checkFieldPathChecklist.js.map +1 -0
  38. package/lib/module/helpers/createFragmentSpreadChecklist.js +15 -0
  39. package/lib/module/helpers/createFragmentSpreadChecklist.js.map +1 -0
  40. package/lib/module/helpers/filterField.js +81 -0
  41. package/lib/module/helpers/filterField.js.map +1 -0
  42. package/lib/module/helpers/filterFragmentDefinitions.js +39 -0
  43. package/lib/module/helpers/filterFragmentDefinitions.js.map +1 -0
  44. package/lib/module/helpers/filterFragmentSpreads.js +23 -0
  45. package/lib/module/helpers/filterFragmentSpreads.js.map +1 -0
  46. package/lib/module/helpers/filterIDsAndTypeNames.js +36 -0
  47. package/lib/module/helpers/filterIDsAndTypeNames.js.map +1 -0
  48. package/lib/module/helpers/filterInlineFragments.js +32 -0
  49. package/lib/module/helpers/filterInlineFragments.js.map +1 -0
  50. package/lib/module/helpers/filterOutPropsWithArgsOrDirectives.js +25 -0
  51. package/lib/module/helpers/filterOutPropsWithArgsOrDirectives.js.map +1 -0
  52. package/lib/module/helpers/filterQuery.js +43 -0
  53. package/lib/module/helpers/filterQuery.js.map +1 -0
  54. package/lib/module/helpers/normalizeResponseData.js +11 -0
  55. package/lib/module/helpers/normalizeResponseData.js.map +1 -0
  56. package/lib/module/helpers/validTypeIDValue.js +8 -0
  57. package/lib/module/helpers/validTypeIDValue.js.map +1 -0
  58. package/lib/module/main/index.js +465 -470
  59. package/lib/module/main/index.js.map +1 -1
  60. package/lib/types/debug/log-cache-entry/index.d.ts.map +1 -1
  61. package/lib/types/debug/log-cache-query/index.d.ts.map +1 -1
  62. package/lib/types/debug/log-partial-compiled/index.d.ts.map +1 -1
  63. package/lib/types/defs/index.d.ts +19 -9
  64. package/lib/types/defs/index.d.ts.map +1 -1
  65. package/lib/types/helpers/buildKeysAndPaths.d.ts +10 -0
  66. package/lib/types/helpers/buildKeysAndPaths.d.ts.map +1 -0
  67. package/lib/types/helpers/checkFieldPathChecklist.d.ts +4 -0
  68. package/lib/types/helpers/checkFieldPathChecklist.d.ts.map +1 -0
  69. package/lib/types/helpers/createFragmentSpreadChecklist.d.ts +11 -0
  70. package/lib/types/helpers/createFragmentSpreadChecklist.d.ts.map +1 -0
  71. package/lib/types/helpers/filterField.d.ts +6 -0
  72. package/lib/types/helpers/filterField.d.ts.map +1 -0
  73. package/lib/types/helpers/filterFragmentDefinitions.d.ts +10 -0
  74. package/lib/types/helpers/filterFragmentDefinitions.d.ts.map +1 -0
  75. package/lib/types/helpers/filterFragmentSpreads.d.ts +6 -0
  76. package/lib/types/helpers/filterFragmentSpreads.d.ts.map +1 -0
  77. package/lib/types/helpers/filterIDsAndTypeNames.d.ts +5 -0
  78. package/lib/types/helpers/filterIDsAndTypeNames.d.ts.map +1 -0
  79. package/lib/types/helpers/filterInlineFragments.d.ts +5 -0
  80. package/lib/types/helpers/filterInlineFragments.d.ts.map +1 -0
  81. package/lib/types/helpers/filterOutPropsWithArgsOrDirectives.d.ts +6 -0
  82. package/lib/types/helpers/filterOutPropsWithArgsOrDirectives.d.ts.map +1 -0
  83. package/lib/types/helpers/filterQuery.d.ts +5 -0
  84. package/lib/types/helpers/filterQuery.d.ts.map +1 -0
  85. package/lib/types/helpers/normalizeResponseData.d.ts +10 -0
  86. package/lib/types/helpers/normalizeResponseData.d.ts.map +1 -0
  87. package/lib/types/helpers/validTypeIDValue.d.ts +3 -0
  88. package/lib/types/helpers/validTypeIDValue.d.ts.map +1 -0
  89. package/lib/types/main/index.d.ts +13 -20
  90. package/lib/types/main/index.d.ts.map +1 -1
  91. package/package.json +2 -2
  92. package/src/__snapshots__/index.test.ts.snap +17449 -7185
  93. package/src/debug/log-cache-entry/index.ts +1 -1
  94. package/src/debug/log-partial-compiled/index.ts +1 -1
  95. package/src/defs/index.ts +18 -10
  96. package/src/helpers/buildKeysAndPaths.ts +71 -0
  97. package/src/helpers/checkFieldPathChecklist.ts +21 -0
  98. package/src/helpers/createFragmentSpreadChecklist.ts +17 -0
  99. package/src/helpers/filterField.ts +73 -0
  100. package/src/helpers/filterFragmentDefinitions.ts +40 -0
  101. package/src/helpers/filterFragmentSpreads.ts +28 -0
  102. package/src/helpers/filterIDsAndTypeNames.ts +31 -0
  103. package/src/helpers/filterInlineFragments.ts +29 -0
  104. package/src/helpers/filterOutPropsWithArgsOrDirectives.ts +30 -0
  105. package/src/helpers/filterQuery.ts +38 -0
  106. package/src/helpers/normalizeResponseData.ts +9 -0
  107. package/src/helpers/validTypeIDValue.ts +11 -0
  108. package/src/index.test.ts +179 -3
  109. package/src/main/index.ts +506 -499
package/src/main/index.ts CHANGED
@@ -17,15 +17,10 @@ import {
17
17
  TYPE_NAME_KEY,
18
18
  } from "@graphql-box/core";
19
19
  import {
20
+ FRAGMENT_SPREAD,
20
21
  dehydrateCacheMetadata,
21
- deleteChildFields,
22
- deleteInlineFragments,
23
- getAlias,
24
- getArguments,
25
22
  getChildFields,
26
- getDirectives,
27
- getInlineFragments,
28
- getName,
23
+ getFragmentDefinitions,
29
24
  getOperationDefinitions,
30
25
  hasChildFields,
31
26
  hashRequest,
@@ -35,20 +30,19 @@ import {
35
30
  } from "@graphql-box/helpers";
36
31
  import Cacheability from "cacheability";
37
32
  import { FieldNode, print } from "graphql";
38
- import { cloneDeep, get, isArray, isNumber, isObjectLike, isPlainObject, isUndefined, set, unset } from "lodash";
33
+ import { assign, cloneDeep, get, isArray, isObjectLike, isPlainObject, isUndefined, set, unset } from "lodash";
39
34
  import { CACHE_CONTROL, HEADER_NO_CACHE, METADATA, NO_CACHE } from "../consts";
40
35
  import { logCacheEntry, logCacheQuery, logPartialCompiled } from "../debug";
41
36
  import {
42
37
  AnalyzeQueryResult,
43
38
  AncestorKeysAndPaths,
39
+ CacheManagerContext,
44
40
  CacheManagerDef,
45
41
  CacheManagerInit,
46
42
  CachedAncestorFieldData,
47
- CachedFieldData,
48
43
  CachedResponseData,
49
44
  CachemapOptions,
50
45
  CheckCacheEntryResult,
51
- CheckFieldPathChecklistResult,
52
46
  ClientOptions,
53
47
  ConstructorOptions,
54
48
  DataForCachingEntry,
@@ -57,16 +51,20 @@ import {
57
51
  FieldPathChecklistValue,
58
52
  InitOptions,
59
53
  KeysAndPaths,
60
- KeysAndPathsOptions,
61
54
  MergedCachedFieldData,
62
55
  PartialQueryResponse,
63
56
  PartialQueryResponses,
64
57
  QueryResponseCacheEntry,
65
58
  ResponseDataForCaching,
66
- TypeNames,
59
+ TypeNamesAndKind,
67
60
  UserOptions,
68
61
  } from "../defs";
62
+ import { buildFieldKeysAndPaths } from "../helpers/buildKeysAndPaths";
69
63
  import deriveOpCacheability from "../helpers/deriveOpCacheability";
64
+ import filterOutPropsWithArgsOrDirectives from "../helpers/filterOutPropsWithArgsOrDirectives";
65
+ import filterQuery from "../helpers/filterQuery";
66
+ import normalizeResponseData from "../helpers/normalizeResponseData";
67
+ import { getValidTypeIDValue } from "../helpers/validTypeIDValue";
70
68
 
71
69
  export class CacheManager implements CacheManagerDef {
72
70
  public static async init(options: InitOptions): Promise<CacheManager> {
@@ -81,73 +79,11 @@ export class CacheManager implements CacheManagerDef {
81
79
  errors.push(new TypeError(message));
82
80
  }
83
81
 
84
- if (errors.length) return Promise.reject(errors);
85
-
86
- return new CacheManager(options);
87
- }
88
-
89
- private static _analyzeLeafField(
90
- field: FieldNode,
91
- cachedAncestorFieldData: CachedAncestorFieldData,
92
- { data, fieldPathChecklist }: CachedResponseData,
93
- _options: RequestOptions,
94
- _context: RequestContext,
95
- ): void {
96
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, cachedAncestorFieldData);
97
- const { propNameOrIndex, requestFieldPath } = keysAndPaths;
98
- const { dataEntityData, requestFieldPathData, typeName } = cachedAncestorFieldData;
99
-
100
- const cachedFieldData =
101
- CacheManager._getFieldDataFromAncestor(dataEntityData, propNameOrIndex) ||
102
- CacheManager._getFieldDataFromAncestor(requestFieldPathData, propNameOrIndex);
103
-
104
- const typeNames = {
105
- dataTypeName: dataEntityData?.__typename || requestFieldPathData?.__typename,
106
- fieldTypeName: typeName,
107
- };
108
-
109
- CacheManager._setFieldPathChecklist(fieldPathChecklist, { data: cachedFieldData }, requestFieldPath, typeNames);
110
- CacheManager._setCachedData(data, { data: cachedFieldData }, propNameOrIndex);
111
- }
112
-
113
- private static _buildKey(key: string | number, path: string): string {
114
- const paths: (string | number)[] = [];
115
- if (path.length) paths.push(path);
116
- paths.push(key);
117
- return paths.join(".");
118
- }
119
-
120
- private static _buildRequestFieldCacheKey(
121
- name: string,
122
- requestFieldCacheKey: string,
123
- args?: PlainObjectMap,
124
- directives?: PlainObjectMap,
125
- index?: number,
126
- ): string {
127
- let key = `${isNumber(index) ? index : name}`;
128
- if (args) key = `${key}(${JSON.stringify(args)})`;
129
- if (directives) key = `${key}(${JSON.stringify(directives)})`;
130
- return CacheManager._buildKey(key, requestFieldCacheKey);
131
- }
132
-
133
- private static _checkFieldPathChecklist(
134
- fieldPathChecklistValues: FieldPathChecklistValue[] | undefined,
135
- fieldTypeName: string | undefined,
136
- ): CheckFieldPathChecklistResult {
137
- if (!fieldPathChecklistValues || !fieldPathChecklistValues.length) {
138
- return { hasData: false, typeUnused: !!fieldTypeName };
139
- }
140
-
141
- if (fieldPathChecklistValues.length === 1) {
142
- const { hasData, typeName } = fieldPathChecklistValues[0];
143
- const typeUnused = !typeName ? undefined : typeName !== fieldTypeName;
144
- return { hasData, typeUnused };
82
+ if (errors.length) {
83
+ return Promise.reject(errors);
145
84
  }
146
85
 
147
- return {
148
- hasData: fieldPathChecklistValues.some(({ hasData, typeName }) => typeName === fieldTypeName && hasData),
149
- typeUnused: !fieldPathChecklistValues.every(({ typeName }) => typeName === fieldTypeName),
150
- };
86
+ return new CacheManager(options);
151
87
  }
152
88
 
153
89
  private static _countFieldPathChecklist(fieldPathChecklist: FieldPathChecklist): FieldCount {
@@ -163,42 +99,15 @@ export class CacheManager implements CacheManagerDef {
163
99
  }
164
100
 
165
101
  private static _getFieldDataFromAncestor(ancestorFieldData: any, propNameOrIndex: string | number): any {
166
- return isObjectLike(ancestorFieldData) ? ancestorFieldData[propNameOrIndex] : undefined;
167
- }
168
-
169
- private static _getFieldKeysAndPaths(field: FieldNode, options: KeysAndPathsOptions): KeysAndPaths {
170
- const { index, requestFieldCacheKey = "", requestFieldPath = "", responseDataPath = "" } = options;
171
- const name = getName(field) as string;
172
-
173
- const updatedRequestFieldCacheKey = CacheManager._buildRequestFieldCacheKey(
174
- name,
175
- requestFieldCacheKey,
176
- getArguments(field),
177
- getDirectives(field),
178
- index,
179
- );
180
-
181
- const fieldAliasOrName = getAlias(field) || name;
182
-
183
- const updatedRequestFieldPath = isNumber(index)
184
- ? requestFieldPath
185
- : CacheManager._buildKey(fieldAliasOrName, requestFieldPath);
186
-
187
- const propNameOrIndex = isNumber(index) ? index : fieldAliasOrName;
188
- const updatedResponseDataPath = CacheManager._buildKey(propNameOrIndex, responseDataPath);
189
-
190
- return {
191
- hashedRequestFieldCacheKey: hashRequest(updatedRequestFieldCacheKey),
192
- propNameOrIndex,
193
- requestFieldCacheKey: updatedRequestFieldCacheKey,
194
- requestFieldPath: updatedRequestFieldPath,
195
- responseDataPath: updatedResponseDataPath,
196
- };
102
+ return isObjectLike(ancestorFieldData) ? cloneDeep(ancestorFieldData[propNameOrIndex]) : undefined;
197
103
  }
198
104
 
199
105
  private static _getOperationCacheControl(cacheMetadata: CacheMetadata | undefined, operation: string): string {
200
106
  const defaultCacheControl = HEADER_NO_CACHE;
201
- if (!cacheMetadata) return defaultCacheControl;
107
+
108
+ if (!cacheMetadata) {
109
+ return defaultCacheControl;
110
+ }
202
111
 
203
112
  const cacheability = cacheMetadata.get(operation);
204
113
  return cacheability ? cacheability.printCacheControl() : defaultCacheControl;
@@ -208,22 +117,26 @@ export class CacheManager implements CacheManagerDef {
208
117
  cacheMetadata: CacheMetadata,
209
118
  partialQueryResponse?: PartialQueryResponse,
210
119
  ): CacheMetadata {
211
- if (!partialQueryResponse) return cacheMetadata;
120
+ if (!partialQueryResponse) {
121
+ return cacheMetadata;
122
+ }
212
123
 
213
124
  return new Map([...partialQueryResponse.cacheMetadata, ...cacheMetadata]);
214
125
  }
215
126
 
216
- private static _isDataEntity(fieldTypeInfo?: FieldTypeInfo): boolean {
217
- if (!fieldTypeInfo) return false;
127
+ private static _isNodeEntity(fieldTypeInfo?: FieldTypeInfo): boolean {
128
+ if (!fieldTypeInfo) {
129
+ return false;
130
+ }
218
131
 
219
132
  const { isEntity, possibleTypes } = fieldTypeInfo;
220
133
  return isEntity || possibleTypes.some(type => !!type.isEntity);
221
134
  }
222
135
 
223
- private static _isRequestFieldPath(fieldTypeInfo?: FieldTypeInfo): boolean {
136
+ private static _isNodeRequestFieldPath(fieldTypeInfo?: FieldTypeInfo): boolean {
224
137
  return (
225
138
  !!fieldTypeInfo &&
226
- (this._isDataEntity(fieldTypeInfo) || fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives)
139
+ (this._isNodeEntity(fieldTypeInfo) || fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives)
227
140
  );
228
141
  }
229
142
 
@@ -249,12 +162,12 @@ export class CacheManager implements CacheManagerDef {
249
162
  cachedFieldData: MergedCachedFieldData,
250
163
  { cacheMetadata, data, fieldPathChecklist }: CachedResponseData,
251
164
  { propNameOrIndex, requestFieldPath }: KeysAndPaths,
252
- typeNames: TypeNames,
165
+ typeNamesAndKind: TypeNamesAndKind,
253
166
  _options: RequestOptions,
254
- { operation }: RequestContext,
167
+ { operation }: CacheManagerContext,
255
168
  ) {
256
169
  CacheManager._setCacheMetadata(cacheMetadata, cachedFieldData.cacheability, requestFieldPath, operation);
257
- CacheManager._setFieldPathChecklist(fieldPathChecklist, cachedFieldData, requestFieldPath, typeNames);
170
+ CacheManager._setFieldPathChecklist(fieldPathChecklist, cachedFieldData, requestFieldPath, typeNamesAndKind);
258
171
  CacheManager._setCachedData(data, cachedFieldData, propNameOrIndex);
259
172
  }
260
173
 
@@ -264,7 +177,9 @@ export class CacheManager implements CacheManagerDef {
264
177
  requestFieldPath: string,
265
178
  operation: string,
266
179
  ): void {
267
- if (!cacheability) return;
180
+ if (!cacheability) {
181
+ return;
182
+ }
268
183
 
269
184
  cacheMetadata.set(requestFieldPath, cacheability);
270
185
  const operationCacheability = cacheMetadata.get(operation);
@@ -278,23 +193,31 @@ export class CacheManager implements CacheManagerDef {
278
193
  fieldPathChecklist: FieldPathChecklist,
279
194
  { data }: MergedCachedFieldData,
280
195
  requestFieldPath: string,
281
- { dataTypeName, fieldTypeName }: TypeNames,
196
+ { dataTypeName, fieldTypeName, fragmentKind, fragmentName }: TypeNamesAndKind,
282
197
  ): void {
283
- if (isUndefined(fieldTypeName)) {
284
- if (fieldPathChecklist.has(requestFieldPath)) return;
285
- fieldPathChecklist.set(requestFieldPath, [{ hasData: !isUndefined(data) }]);
198
+ if (isUndefined(fieldTypeName) || fragmentKind === FRAGMENT_SPREAD) {
199
+ if (fieldPathChecklist.has(requestFieldPath)) {
200
+ return;
201
+ }
202
+
203
+ fieldPathChecklist.set(requestFieldPath, [{ fragmentKind, fragmentName, hasData: !isUndefined(data) }]);
286
204
  return;
287
205
  }
288
206
 
289
- if (dataTypeName !== fieldTypeName) return;
207
+ if (dataTypeName !== fieldTypeName) {
208
+ return;
209
+ }
290
210
 
291
211
  const entry = fieldPathChecklist.get(requestFieldPath);
292
212
  const checklistValues = entry ? (entry as FieldPathChecklistValue[]) : [];
293
- if (checklistValues.some(({ typeName }) => typeName === dataTypeName)) return;
213
+
214
+ if (checklistValues.some(({ typeName }) => typeName === dataTypeName)) {
215
+ return;
216
+ }
294
217
 
295
218
  fieldPathChecklist.set(requestFieldPath, [
296
219
  ...checklistValues,
297
- { hasData: !isUndefined(data), typeName: dataTypeName as string },
220
+ { fragmentKind, fragmentName, hasData: !isUndefined(data), typeName: dataTypeName as string },
298
221
  ]);
299
222
  }
300
223
 
@@ -329,22 +252,35 @@ export class CacheManager implements CacheManagerDef {
329
252
  return Promise.reject(new TypeError("@graphql-box/cache-manager expected an AST."));
330
253
  }
331
254
 
332
- const cachedResponseData = await this._getCachedResponseData(requestData, options, context);
255
+ const cacheManagerContext: CacheManagerContext = {
256
+ ...context,
257
+ fragmentDefinitions: getFragmentDefinitions(ast),
258
+ typeIDKey: this._typeIDKey,
259
+ };
333
260
 
261
+ const cachedResponseData = await this._retrieveCachedResponseData(requestData, options, cacheManagerContext);
334
262
  const { cacheMetadata, data, fieldCount } = cachedResponseData;
335
- if (fieldCount.missing === fieldCount.total) return { updated: requestData };
263
+
264
+ if (fieldCount.missing === fieldCount.total) {
265
+ return { updated: requestData };
266
+ }
336
267
 
337
268
  if (!fieldCount.missing) {
338
- const dataCaching = this._setQueryResponseCacheEntry(hash, { cacheMetadata, data }, options, context);
339
- if (options.awaitDataCaching) await dataCaching;
269
+ const dataCaching = this._setQueryResponseCacheEntry(hash, { cacheMetadata, data }, options, cacheManagerContext);
270
+
271
+ if (options.awaitDataCaching) {
272
+ await dataCaching;
273
+ }
340
274
 
341
275
  return { response: { cacheMetadata, data } };
342
276
  }
343
277
 
344
- this._setPartialQueryResponse(hash, { cacheMetadata, data }, options, context);
345
- this._filterQuery(requestData, cachedResponseData, context);
346
- const request = print(ast);
347
- return { updated: { ast, hash: hashRequest(request), request } };
278
+ this._setPartialQueryResponse(hash, { cacheMetadata, data }, options, cacheManagerContext);
279
+ const filteredAST = filterQuery(requestData, cachedResponseData, cacheManagerContext);
280
+ const { fragmentDefinitions, typeIDKey, ...rest } = cacheManagerContext;
281
+ assign(context, rest);
282
+ const request = print(filteredAST);
283
+ return { updated: { ast: filteredAST, hash: hashRequest(request), request } };
348
284
  }
349
285
 
350
286
  public async checkCacheEntry(
@@ -363,7 +299,9 @@ export class CacheManager implements CacheManagerDef {
363
299
  ): Promise<ResponseData | false> {
364
300
  const result = await this._checkCacheEntry(QUERY_RESPONSES, hash, options, context);
365
301
 
366
- if (!result) return false;
302
+ if (!result) {
303
+ return false;
304
+ }
367
305
 
368
306
  const { cacheMetadata, data } = result.entry as QueryResponseCacheEntry;
369
307
 
@@ -384,14 +322,31 @@ export class CacheManager implements CacheManagerDef {
384
322
  options: RequestOptions,
385
323
  context: RequestContext,
386
324
  ): Promise<ResponseData> {
325
+ const cacheManagerContext: CacheManagerContext = {
326
+ ...context,
327
+ fragmentDefinitions: getFragmentDefinitions(updatedRequestData.ast),
328
+ typeIDKey: this._typeIDKey,
329
+ };
330
+
387
331
  const dataCaching: Promise<void>[] = [];
388
- const { cacheMetadata, data } = await this._resolveRequest(updatedRequestData, rawResponseData, options, context);
332
+
333
+ const { cacheMetadata, data, hasNext } = await this._resolveRequest(
334
+ updatedRequestData,
335
+ rawResponseData,
336
+ options,
337
+ cacheManagerContext,
338
+ );
389
339
 
390
340
  let partialQueryResponse: PartialQueryResponse | undefined;
391
341
 
392
- if (context.queryFiltered) {
342
+ if (cacheManagerContext.queryFiltered) {
393
343
  dataCaching.push(
394
- this._setQueryResponseCacheEntry(updatedRequestData.hash, { cacheMetadata, data }, options, context),
344
+ this._setQueryResponseCacheEntry(
345
+ updatedRequestData.hash,
346
+ { cacheMetadata, data },
347
+ options,
348
+ cacheManagerContext,
349
+ ),
395
350
  );
396
351
 
397
352
  partialQueryResponse = this._getPartialQueryResponse(requestData.hash);
@@ -405,13 +360,15 @@ export class CacheManager implements CacheManagerDef {
405
360
  requestData.hash,
406
361
  { cacheMetadata: responseCacheMetadata, data: responseData },
407
362
  options,
408
- context,
363
+ cacheManagerContext,
409
364
  ),
410
365
  );
411
366
 
412
- if (options.awaitDataCaching) await Promise.all(dataCaching);
367
+ if (options.awaitDataCaching) {
368
+ await Promise.all(dataCaching);
369
+ }
413
370
 
414
- return { cacheMetadata: responseCacheMetadata, data: responseData };
371
+ return { cacheMetadata: responseCacheMetadata, data: responseData, hasNext };
415
372
  }
416
373
 
417
374
  public async resolveRequest(
@@ -420,82 +377,141 @@ export class CacheManager implements CacheManagerDef {
420
377
  options: RequestOptions,
421
378
  context: RequestContext,
422
379
  ): Promise<ResponseData> {
423
- return this._resolveRequest(requestData, rawResponseData, options, context);
380
+ const cacheManagerContext: CacheManagerContext = {
381
+ ...context,
382
+ fragmentDefinitions: getFragmentDefinitions(requestData.ast),
383
+ typeIDKey: this._typeIDKey,
384
+ };
385
+
386
+ return this._resolveRequest(requestData, rawResponseData, options, cacheManagerContext);
424
387
  }
425
388
 
426
- private async _analyzeField(
427
- field: FieldNode,
389
+ private async _analyzeFieldNode(
390
+ fieldNode: FieldNode,
428
391
  cachedAncestorFieldData: CachedAncestorFieldData,
429
392
  cachedResponseData: CachedResponseData,
430
393
  options: RequestOptions,
431
- context: RequestContext,
394
+ context: CacheManagerContext,
432
395
  ): Promise<void> {
433
- if (hasChildFields(field)) {
434
- await this._analyzeParentField(field, cachedAncestorFieldData, cachedResponseData, options, context);
396
+ if (hasChildFields(fieldNode)) {
397
+ await this._analyzeParentFieldNode(fieldNode, cachedAncestorFieldData, cachedResponseData, options, context);
435
398
  } else {
436
- await CacheManager._analyzeLeafField(field, cachedAncestorFieldData, cachedResponseData, options, context);
399
+ await this._analyzeLeafFieldNode(fieldNode, cachedAncestorFieldData, cachedResponseData, options, context);
437
400
  }
438
401
  }
439
402
 
440
- private async _analyzeParentField(
441
- field: FieldNode,
403
+ private async _analyzeLeafFieldNode(
404
+ fieldNode: FieldNode,
442
405
  cachedAncestorFieldData: CachedAncestorFieldData,
443
406
  cachedResponseData: CachedResponseData,
444
407
  options: RequestOptions,
445
- context: RequestContext,
408
+ context: CacheManagerContext,
446
409
  ): Promise<void> {
447
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, cachedAncestorFieldData);
448
- const { hashedRequestFieldCacheKey, propNameOrIndex, requestFieldCacheKey, requestFieldPath } = keysAndPaths;
410
+ const keysAndPaths = buildFieldKeysAndPaths(fieldNode, cachedAncestorFieldData, context);
411
+ const { hashedRequestFieldCacheKey, propNameOrIndex, requestFieldPath } = keysAndPaths;
449
412
  const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
413
+ const { entityData, fragmentKind, fragmentName, requestFieldPathData, typeName } = cachedAncestorFieldData;
450
414
 
451
- const {
452
- dataEntityData: ancestorDataEntityData,
453
- requestFieldPathData: ancestorRequestFieldPathData,
454
- typeName,
455
- } = cachedAncestorFieldData;
456
-
457
- const cachedFieldData: CachedFieldData = {
458
- dataEntityData: CacheManager._getFieldDataFromAncestor(ancestorDataEntityData, propNameOrIndex),
459
- requestFieldPathData: CacheManager._getFieldDataFromAncestor(ancestorRequestFieldPathData, propNameOrIndex),
415
+ const typeNamesAndKind = {
416
+ dataTypeName: entityData?.__typename || requestFieldPathData?.__typename,
417
+ fieldTypeName: typeName,
418
+ fragmentKind,
419
+ fragmentName,
460
420
  };
461
421
 
462
- if (CacheManager._isRequestFieldPath(fieldTypeInfo)) {
463
- await this._setRequestFieldPathData(cachedFieldData, hashedRequestFieldCacheKey, options, context);
464
- }
422
+ if (CacheManager._isNodeRequestFieldPath(fieldTypeInfo)) {
423
+ const { cacheability, entry } = await this._retrieveCachedRequestFieldPathData(
424
+ hashedRequestFieldCacheKey,
425
+ options,
426
+ context,
427
+ );
428
+
429
+ CacheManager._setCachedResponseData(
430
+ { cacheability, data: entry },
431
+ cachedResponseData,
432
+ keysAndPaths,
433
+ typeNamesAndKind,
434
+ options,
435
+ context,
436
+ );
437
+ } else {
438
+ const cachedFieldData =
439
+ CacheManager._getFieldDataFromAncestor(entityData, propNameOrIndex) ||
440
+ CacheManager._getFieldDataFromAncestor(requestFieldPathData, propNameOrIndex);
465
441
 
466
- if (CacheManager._isDataEntity(fieldTypeInfo)) {
467
- await this._setDataEntityData(cachedFieldData, fieldTypeInfo as FieldTypeInfo, options, context);
442
+ CacheManager._setFieldPathChecklist(
443
+ cachedResponseData.fieldPathChecklist,
444
+ { data: cachedFieldData },
445
+ requestFieldPath,
446
+ typeNamesAndKind,
447
+ );
448
+
449
+ CacheManager._setCachedData(cachedResponseData.data, { data: cachedFieldData }, propNameOrIndex);
468
450
  }
451
+ }
469
452
 
470
- const { cacheability, dataEntityData, requestFieldPathData } = cachedFieldData;
453
+ private async _analyzeParentFieldNode(
454
+ fieldNode: FieldNode,
455
+ cachedAncestorFieldData: CachedAncestorFieldData,
456
+ cachedResponseData: CachedResponseData,
457
+ options: RequestOptions,
458
+ context: CacheManagerContext,
459
+ ): Promise<void> {
460
+ const keysAndPaths = buildFieldKeysAndPaths(fieldNode, cachedAncestorFieldData, context);
461
+ const { propNameOrIndex, requestFieldCacheKey, requestFieldPath } = keysAndPaths;
462
+ const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath) as FieldTypeInfo;
471
463
 
472
- const data =
473
- !isUndefined(requestFieldPathData) || !isUndefined(dataEntityData)
474
- ? this._mergeObjects(requestFieldPathData, dataEntityData)
475
- : undefined;
464
+ const { cacheability, data, entityData, requestFieldPathData } = await this._retrieveCachedParentNodeData(
465
+ cachedAncestorFieldData,
466
+ keysAndPaths,
467
+ fieldTypeInfo,
468
+ options,
469
+ context,
470
+ );
471
+
472
+ const { fragmentKind, fragmentName, typeName } = cachedAncestorFieldData;
476
473
 
477
474
  CacheManager._setCachedResponseData(
478
475
  { cacheability, data },
479
476
  cachedResponseData,
480
477
  keysAndPaths,
481
- { dataTypeName: get(data, TYPE_NAME_KEY), fieldTypeName: typeName },
478
+ { dataTypeName: get(data, TYPE_NAME_KEY), fieldTypeName: typeName, fragmentKind, fragmentName },
482
479
  options,
483
480
  context,
484
481
  );
485
482
 
486
- if (!isObjectLike(data)) return;
483
+ if (!isObjectLike(data)) {
484
+ return;
485
+ }
487
486
 
488
487
  const objectLikeData = data as PlainObjectMap | any[];
489
488
  const promises: Promise<void>[] = [];
490
489
 
491
490
  iterateChildFields(
492
- field,
491
+ fieldNode,
493
492
  objectLikeData,
494
- (childField: FieldNode, childTypeName: string | undefined, childIndex?: number) => {
493
+ context.fragmentDefinitions,
494
+ (
495
+ childField: FieldNode,
496
+ childTypeName: string | undefined,
497
+ childFragmentKind: string | undefined,
498
+ childFragmentName: string | undefined,
499
+ childIndex?: number,
500
+ ) => {
495
501
  promises.push(
496
- this._analyzeField(
502
+ this._analyzeFieldNode(
497
503
  childField,
498
- { index: childIndex, requestFieldCacheKey, requestFieldPath, typeName: childTypeName, ...cachedFieldData },
504
+ {
505
+ cacheability,
506
+ entityData,
507
+ fragmentKind: childFragmentKind,
508
+ fragmentName: childFragmentName,
509
+ index: childIndex,
510
+ requestFieldCacheKey,
511
+ requestFieldPath,
512
+ requestFieldPathData,
513
+ typeName: childTypeName,
514
+ },
499
515
  { ...cachedResponseData, data: cachedResponseData.data[propNameOrIndex] },
500
516
  options,
501
517
  context,
@@ -511,12 +527,15 @@ export class CacheManager implements CacheManagerDef {
511
527
  { ast }: RequestData,
512
528
  { data, ...otherProps }: RawResponseDataWithMaybeCacheMetadata,
513
529
  options: RequestOptions,
514
- context: RequestContext,
530
+ context: CacheManagerContext,
515
531
  ): CacheMetadata {
516
532
  const cacheMetadata = this._createCacheMetadata({ data, ...otherProps }, context);
517
533
  const queryNode = getOperationDefinitions(ast, context.operation)[0];
518
534
  const fieldsAndTypeNames = getChildFields(queryNode);
519
- if (!fieldsAndTypeNames) return cacheMetadata;
535
+
536
+ if (!fieldsAndTypeNames) {
537
+ return cacheMetadata;
538
+ }
520
539
 
521
540
  fieldsAndTypeNames.forEach(({ fieldNode }) =>
522
541
  this._setFieldCacheability(
@@ -535,16 +554,20 @@ export class CacheManager implements CacheManagerDef {
535
554
  cacheType: CacheTypes,
536
555
  hash: string,
537
556
  options: RequestOptions,
538
- context: RequestContext,
557
+ context: CacheManagerContext,
539
558
  ): Promise<CheckCacheEntryResult | false> {
540
559
  try {
541
560
  const cacheability = await this._hasCacheEntry(cacheType, hash);
542
561
 
543
- if (!cacheability || !CacheManager._isValid(cacheability)) return false;
562
+ if (!cacheability || !CacheManager._isValid(cacheability)) {
563
+ return false;
564
+ }
544
565
 
545
566
  const entry = await this._getCacheEntry(cacheType, hash, options, context);
546
567
 
547
- if (!entry) return false;
568
+ if (isUndefined(entry)) {
569
+ return false;
570
+ }
548
571
 
549
572
  return { cacheability, entry };
550
573
  } catch (error) {
@@ -554,7 +577,7 @@ export class CacheManager implements CacheManagerDef {
554
577
 
555
578
  private _createCacheMetadata(
556
579
  { _cacheMetadata, headers }: RawResponseDataWithMaybeCacheMetadata,
557
- { operation }: RequestContext,
580
+ { operation }: CacheManagerContext,
558
581
  ): CacheMetadata {
559
582
  const cacheMetadata = new Map();
560
583
 
@@ -573,151 +596,13 @@ export class CacheManager implements CacheManagerDef {
573
596
  return cacheMetadata;
574
597
  }
575
598
 
576
- private _filterField(
577
- field: FieldNode,
578
- fieldPathChecklist: FieldPathChecklist,
579
- ancestorRequestFieldPath: string,
580
- context: RequestContext,
581
- ): boolean {
582
- const fieldsAndTypeNames = getChildFields(field);
583
- if (!fieldsAndTypeNames) return false;
584
-
585
- for (let i = fieldsAndTypeNames.length - 1; i >= 0; i -= 1) {
586
- const { fieldNode: childField, typeName: childTypeName } = fieldsAndTypeNames[i];
587
- const childFieldName = getName(childField);
588
-
589
- if (childFieldName === this._typeIDKey || childFieldName === TYPE_NAME_KEY) continue;
590
-
591
- const { requestFieldPath } = CacheManager._getFieldKeysAndPaths(childField, {
592
- requestFieldPath: ancestorRequestFieldPath,
593
- });
594
-
595
- const { hasData, typeUnused } = CacheManager._checkFieldPathChecklist(
596
- fieldPathChecklist.get(requestFieldPath),
597
- childTypeName,
598
- );
599
-
600
- if (hasData || typeUnused) {
601
- if (!hasChildFields(childField)) {
602
- deleteChildFields(field, childField);
603
- } else if (this._filterField(childField, fieldPathChecklist, requestFieldPath, context)) {
604
- deleteChildFields(field, childField);
605
- }
606
- }
607
- }
608
-
609
- this._filterInlineFragments(field);
610
- this._filterIDsAndTypeNames(field);
611
- return !hasChildFields(field);
612
- }
613
-
614
- private _filterIDsAndTypeNames(field: FieldNode): boolean {
615
- const fieldsAndTypeNames = getChildFields(field);
616
- if (!fieldsAndTypeNames || fieldsAndTypeNames.length > 3) return false;
617
-
618
- const fieldNames = fieldsAndTypeNames.map(({ fieldNode }) => getName(fieldNode) as string);
619
-
620
- if (fieldNames.length === 2 && fieldNames.every(name => name === this._typeIDKey || name === TYPE_NAME_KEY)) {
621
- deleteChildFields(
622
- field,
623
- fieldsAndTypeNames.map(({ fieldNode }) => fieldNode),
624
- );
625
-
626
- return true;
627
- }
628
-
629
- if ((fieldNames.length === 1 && fieldNames[0] === this._typeIDKey) || fieldNames[0] === TYPE_NAME_KEY) {
630
- const { fieldNode } = fieldsAndTypeNames[0];
631
- deleteChildFields(field, fieldNode);
632
- return true;
633
- }
634
-
635
- return false;
636
- }
637
-
638
- private _filterInlineFragments(field: FieldNode): boolean {
639
- const inlineFragments = getInlineFragments(field);
640
- let filtered = false;
641
-
642
- inlineFragments.forEach(fragment => {
643
- const fieldsAndTypeNames = getChildFields(fragment);
644
-
645
- if (!fieldsAndTypeNames || !fieldsAndTypeNames.length) {
646
- deleteInlineFragments(field, fragment);
647
- filtered = true;
648
- return;
649
- }
650
-
651
- if (fieldsAndTypeNames.length === 1) {
652
- const { fieldNode } = fieldsAndTypeNames[0];
653
-
654
- if (getName(fieldNode) === this._typeIDKey) {
655
- deleteInlineFragments(field, fragment);
656
- filtered = true;
657
- }
658
- }
659
- });
660
-
661
- return filtered;
662
- }
663
-
664
- private _filterQuery(
665
- { ast }: RequestData,
666
- { fieldPathChecklist }: CachedResponseData,
667
- context: RequestContext,
668
- ): void {
669
- const queryNode = getOperationDefinitions(ast, context.operation)[0];
670
- const fieldsAndTypeNames = getChildFields(queryNode);
671
- if (!fieldsAndTypeNames) return;
672
-
673
- for (let i = fieldsAndTypeNames.length - 1; i >= 0; i -= 1) {
674
- const { fieldNode } = fieldsAndTypeNames[i];
675
-
676
- const { requestFieldPath } = CacheManager._getFieldKeysAndPaths(fieldNode, {
677
- requestFieldPath: context.operation,
678
- });
679
-
680
- if (this._filterField(fieldNode, fieldPathChecklist, requestFieldPath, context)) {
681
- deleteChildFields(queryNode, fieldNode);
682
- }
683
- }
684
-
685
- context.queryFiltered = true;
686
- }
687
-
688
- private async _getCachedResponseData(
689
- { ast }: RequestData,
690
- options: RequestOptions,
691
- context: RequestContext,
692
- ): Promise<CachedResponseData> {
693
- const cachedResponseData: CachedResponseData = {
694
- cacheMetadata: new Map(),
695
- data: {},
696
- fieldCount: { missing: 0, total: 0 },
697
- fieldPathChecklist: new Map(),
698
- };
699
-
700
- const queryNode = getOperationDefinitions(ast, context.operation)[0];
701
- const fieldsAndTypeNames = getChildFields(queryNode);
702
- if (!fieldsAndTypeNames) return cachedResponseData;
703
-
704
- await Promise.all(
705
- fieldsAndTypeNames.map(({ fieldNode }) =>
706
- this._analyzeField(fieldNode, { requestFieldPath: context.operation }, cachedResponseData, options, context),
707
- ),
708
- );
709
-
710
- cachedResponseData.fieldCount = CacheManager._countFieldPathChecklist(cachedResponseData.fieldPathChecklist);
711
- return cachedResponseData;
712
- }
713
-
714
599
  @logCacheQuery()
715
600
  private async _getCacheEntry(
716
601
  cacheType: CacheTypes,
717
602
  hash: string,
718
603
  _options: RequestOptions,
719
- _context: RequestContext,
720
- ): Promise<ResponseData> {
604
+ _context: CacheManagerContext,
605
+ ): Promise<any> {
721
606
  try {
722
607
  return await this._cache.get(`${cacheType}::${hash}`);
723
608
  } catch (errors) {
@@ -732,7 +617,9 @@ export class CacheManager implements CacheManagerDef {
732
617
  }
733
618
 
734
619
  private _getResponseData(responseData: PlainObjectMap, partialQueryResponse?: PartialQueryResponse): PlainObjectMap {
735
- if (!partialQueryResponse) return responseData;
620
+ if (!partialQueryResponse) {
621
+ return responseData;
622
+ }
736
623
 
737
624
  return this._mergeObjects(partialQueryResponse.data, responseData);
738
625
  }
@@ -746,9 +633,18 @@ export class CacheManager implements CacheManagerDef {
746
633
  }
747
634
 
748
635
  private _isFieldEntity(fieldData: any, { isEntity, possibleTypes }: FieldTypeInfo): boolean {
749
- if (!get(fieldData, this._typeIDKey, null)) return false;
750
- if (isEntity) return true;
751
- if (!possibleTypes.length) return false;
636
+ if (!get(fieldData, this._typeIDKey, null)) {
637
+ return false;
638
+ }
639
+
640
+ if (isEntity) {
641
+ return true;
642
+ }
643
+
644
+ if (!possibleTypes.length) {
645
+ return false;
646
+ }
647
+
752
648
  return possibleTypes.some(type => type.typeName === fieldData.__typename);
753
649
  }
754
650
 
@@ -758,43 +654,55 @@ export class CacheManager implements CacheManagerDef {
758
654
  });
759
655
  }
760
656
 
761
- private async _parseFieldDataEntityAndRequestFieldPathCacheEntryData(
657
+ private async _parseEntityAndRequestFieldPathCacheEntryData(
762
658
  field: FieldNode,
763
659
  ancestorKeysAndPaths: AncestorKeysAndPaths,
764
- { cacheMetadata, dataEntityData, requestFieldPathData }: ResponseDataForCaching,
660
+ { cacheMetadata, entityData, requestFieldPathData }: ResponseDataForCaching,
765
661
  options: RequestOptions,
766
- context: RequestContext,
662
+ context: CacheManagerContext,
767
663
  ): Promise<void> {
768
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, ancestorKeysAndPaths);
664
+ const keysAndPaths = buildFieldKeysAndPaths(field, ancestorKeysAndPaths, context);
769
665
  const { requestFieldCacheKey, requestFieldPath, responseDataPath } = keysAndPaths;
770
- const fieldData = get(requestFieldPathData, responseDataPath, null);
771
- if (!isObjectLike(fieldData)) return;
666
+ const fieldData = get(requestFieldPathData, responseDataPath);
667
+ const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
772
668
 
773
- const objectLikeFieldData = fieldData as PlainObjectMap | any[];
774
- const promises: Promise<void>[] = [];
669
+ if (!isObjectLike(fieldData) && !fieldTypeInfo?.hasDirectives) {
670
+ return;
671
+ }
775
672
 
776
- iterateChildFields(
777
- field,
778
- objectLikeFieldData,
779
- (childField: FieldNode, _typeName: string | undefined, childIndex?: number) => {
780
- promises.push(
781
- this._parseFieldDataEntityAndRequestFieldPathCacheEntryData(
782
- childField,
783
- { index: childIndex, requestFieldCacheKey, requestFieldPath, responseDataPath },
784
- { cacheMetadata, dataEntityData, requestFieldPathData },
785
- options,
786
- context,
787
- ),
788
- );
789
- },
790
- );
673
+ if (isObjectLike(fieldData)) {
674
+ const promises: Promise<void>[] = [];
791
675
 
792
- await Promise.all(promises);
676
+ iterateChildFields(
677
+ field,
678
+ fieldData as PlainObjectMap | any[],
679
+ context.fragmentDefinitions,
680
+ (
681
+ childField: FieldNode,
682
+ _typeName: string | undefined,
683
+ _fragmentKind: string | undefined,
684
+ _fragmentName: string | undefined,
685
+ childIndex?: number,
686
+ ) => {
687
+ promises.push(
688
+ this._parseEntityAndRequestFieldPathCacheEntryData(
689
+ childField,
690
+ { index: childIndex, requestFieldCacheKey, requestFieldPath, responseDataPath },
691
+ { cacheMetadata, entityData, requestFieldPathData },
692
+ options,
693
+ context,
694
+ ),
695
+ );
696
+ },
697
+ );
698
+
699
+ await Promise.all(promises);
700
+ }
793
701
 
794
- await this._setFieldDataEntityAndRequestFieldPathCacheEntry(
702
+ await this._setEntityAndRequestFieldPathCacheEntry(
795
703
  field,
796
704
  keysAndPaths,
797
- { cacheMetadata, dataEntityData, requestFieldPathData },
705
+ { cacheMetadata, entityData, requestFieldPathData },
798
706
  options,
799
707
  context,
800
708
  );
@@ -804,24 +712,158 @@ export class CacheManager implements CacheManagerDef {
804
712
  requestData: RequestData,
805
713
  rawResponseData: RawResponseDataWithMaybeCacheMetadata,
806
714
  options: RequestOptions,
807
- context: RequestContext,
715
+ context: CacheManagerContext,
808
716
  ): Promise<ResponseData> {
717
+ const normalizedResponseData = rawResponseData.path ? normalizeResponseData(rawResponseData) : rawResponseData;
809
718
  const dataCaching: Promise<void>[] = [];
810
- const cacheMetadata = this._buildCacheMetadata(requestData, rawResponseData, options, context);
811
- const { data } = rawResponseData;
719
+ const cacheMetadata = this._buildCacheMetadata(requestData, normalizedResponseData, options, context);
720
+ const { data, hasNext } = normalizedResponseData;
812
721
 
813
722
  dataCaching.push(
814
- this._setDataEntityAndRequestFieldPathCacheEntries(
723
+ this._setEntityAndRequestFieldPathCacheEntries(
815
724
  requestData,
816
- { cacheMetadata, dataEntityData: cloneDeep(data), requestFieldPathData: cloneDeep(data) },
725
+ { cacheMetadata, entityData: cloneDeep(data), requestFieldPathData: cloneDeep(data) },
817
726
  options,
818
727
  context,
819
728
  ),
820
729
  );
821
730
 
822
- if (options.awaitDataCaching) await Promise.all(dataCaching);
731
+ if (options.awaitDataCaching) {
732
+ await Promise.all(dataCaching);
733
+ }
734
+
735
+ return { cacheMetadata, data, hasNext };
736
+ }
737
+
738
+ private async _retrieveCachedEntityData(
739
+ validTypeIDValue: string | number,
740
+ { possibleTypes, typeName }: FieldTypeInfo,
741
+ options: RequestOptions,
742
+ context: CacheManagerContext,
743
+ ) {
744
+ const typeNames = [...possibleTypes.map(type => type.typeName), typeName];
745
+
746
+ const checkResults = await Promise.all(
747
+ typeNames.map(name => this._checkCacheEntry(DATA_ENTITIES, `${name}::${validTypeIDValue}`, options, context)),
748
+ );
749
+
750
+ const validResults = checkResults.filter(result => !!result) as CheckCacheEntryResult[];
751
+ let validResult: CheckCacheEntryResult | undefined;
752
+
753
+ if (validResults.length === 1) {
754
+ validResult = validResults[0];
755
+ } else if (validResults.length > 1) {
756
+ validResults.sort(({ cacheability: a }, { cacheability: b }) => a.metadata.ttl - b.metadata.ttl);
757
+
758
+ validResult = {
759
+ cacheability: validResults[0].cacheability,
760
+ entry: validResults.reduce((obj, { entry }) => this._mergeObjects(obj, entry), {}),
761
+ };
762
+ }
823
763
 
824
- return { cacheMetadata, data };
764
+ return (validResult || {}) as Partial<CheckCacheEntryResult>;
765
+ }
766
+
767
+ private async _retrieveCachedParentNodeData(
768
+ { entityData: ancestorEntityData, requestFieldPathData: ancestorRequestFieldPathData }: CachedAncestorFieldData,
769
+ { hashedRequestFieldCacheKey, propNameOrIndex }: KeysAndPaths,
770
+ fieldTypeInfo: FieldTypeInfo,
771
+ options: RequestOptions,
772
+ context: CacheManagerContext,
773
+ ) {
774
+ let entityData = CacheManager._getFieldDataFromAncestor(ancestorEntityData, propNameOrIndex);
775
+ let requestFieldPathData = CacheManager._getFieldDataFromAncestor(ancestorRequestFieldPathData, propNameOrIndex);
776
+ let cacheability: Cacheability | undefined;
777
+
778
+ if (CacheManager._isNodeRequestFieldPath(fieldTypeInfo)) {
779
+ const { cacheability: entryCacheability, entry } = await this._retrieveCachedRequestFieldPathData(
780
+ hashedRequestFieldCacheKey,
781
+ options,
782
+ context,
783
+ );
784
+
785
+ if (entry) {
786
+ requestFieldPathData = this._mergeObjects(requestFieldPathData, entry);
787
+ }
788
+
789
+ if (entryCacheability) {
790
+ cacheability = entryCacheability;
791
+ }
792
+ }
793
+
794
+ const validTypeIDValue = getValidTypeIDValue(requestFieldPathData, fieldTypeInfo, this._typeIDKey);
795
+
796
+ if (CacheManager._isNodeEntity(fieldTypeInfo) && validTypeIDValue) {
797
+ const { cacheability: entryCacheability, entry } = await this._retrieveCachedEntityData(
798
+ validTypeIDValue,
799
+ fieldTypeInfo,
800
+ options,
801
+ context,
802
+ );
803
+
804
+ if (entry) {
805
+ entityData = this._mergeObjects(entityData, entry);
806
+ }
807
+
808
+ if (entryCacheability && (!cacheability || entryCacheability.metadata.ttl > cacheability?.metadata.ttl)) {
809
+ cacheability = entryCacheability;
810
+ }
811
+ }
812
+
813
+ const data =
814
+ !isUndefined(requestFieldPathData) || !isUndefined(entityData)
815
+ ? this._mergeObjects(requestFieldPathData, entityData)
816
+ : entityData ?? requestFieldPathData;
817
+
818
+ return {
819
+ cacheability,
820
+ data,
821
+ entityData,
822
+ requestFieldPathData,
823
+ };
824
+ }
825
+
826
+ private async _retrieveCachedRequestFieldPathData(
827
+ hash: string,
828
+ options: RequestOptions,
829
+ context: CacheManagerContext,
830
+ ) {
831
+ return (this._checkCacheEntry(REQUEST_FIELD_PATHS, hash, options, context) || {}) as Partial<CheckCacheEntryResult>;
832
+ }
833
+
834
+ private async _retrieveCachedResponseData(
835
+ { ast }: RequestData,
836
+ options: RequestOptions,
837
+ context: CacheManagerContext,
838
+ ): Promise<CachedResponseData> {
839
+ const cachedResponseData: CachedResponseData = {
840
+ cacheMetadata: new Map(),
841
+ data: {},
842
+ fieldCount: { missing: 0, total: 0 },
843
+ fieldPathChecklist: new Map(),
844
+ };
845
+
846
+ const queryNode = getOperationDefinitions(ast, context.operation)[0];
847
+ const fieldsAndTypeNames = getChildFields(queryNode);
848
+
849
+ if (!fieldsAndTypeNames) {
850
+ return cachedResponseData;
851
+ }
852
+
853
+ await Promise.all(
854
+ fieldsAndTypeNames.map(({ fieldNode }) =>
855
+ this._analyzeFieldNode(
856
+ fieldNode,
857
+ { requestFieldPath: context.operation },
858
+ cachedResponseData,
859
+ options,
860
+ context,
861
+ ),
862
+ ),
863
+ );
864
+
865
+ cachedResponseData.fieldCount = CacheManager._countFieldPathChecklist(cachedResponseData.fieldPathChecklist);
866
+ return cachedResponseData;
825
867
  }
826
868
 
827
869
  @logCacheEntry()
@@ -831,7 +873,7 @@ export class CacheManager implements CacheManagerDef {
831
873
  value: any,
832
874
  cachemapOptions: CachemapOptions,
833
875
  _options: RequestOptions,
834
- _context: RequestContext,
876
+ _context: CacheManagerContext,
835
877
  ): Promise<void> {
836
878
  try {
837
879
  await this._cache.set(`${cacheType}::${hash}`, cloneDeep(value), cachemapOptions);
@@ -840,19 +882,22 @@ export class CacheManager implements CacheManagerDef {
840
882
  }
841
883
  }
842
884
 
843
- private async _setDataEntityAndRequestFieldPathCacheEntries(
885
+ private async _setEntityAndRequestFieldPathCacheEntries(
844
886
  requestData: RequestData,
845
887
  responseData: ResponseDataForCaching,
846
888
  options: RequestOptions,
847
- context: RequestContext,
889
+ context: CacheManagerContext,
848
890
  ): Promise<void> {
849
891
  const operationNode = getOperationDefinitions(requestData.ast, context.operation)[0];
850
892
  const fieldsAndTypeNames = getChildFields(operationNode);
851
- if (!fieldsAndTypeNames) return;
893
+
894
+ if (!fieldsAndTypeNames) {
895
+ return;
896
+ }
852
897
 
853
898
  await Promise.all(
854
899
  fieldsAndTypeNames.map(({ fieldNode }) => {
855
- return this._parseFieldDataEntityAndRequestFieldPathCacheEntryData(
900
+ return this._parseEntityAndRequestFieldPathCacheEntryData(
856
901
  fieldNode,
857
902
  { requestFieldPath: context.operation },
858
903
  responseData,
@@ -863,80 +908,74 @@ export class CacheManager implements CacheManagerDef {
863
908
  );
864
909
  }
865
910
 
866
- private async _setDataEntityCacheEntry(
867
- { responseDataPath }: KeysAndPaths,
868
- { cacheability, data, fieldTypeInfo }: DataForCachingEntry,
911
+ private async _setEntityAndRequestFieldPathCacheEntry(
912
+ field: FieldNode,
913
+ keysAndPaths: KeysAndPaths,
914
+ { cacheMetadata, entityData, requestFieldPathData }: ResponseDataForCaching,
869
915
  options: RequestOptions,
870
- context: RequestContext,
916
+ context: CacheManagerContext,
871
917
  ) {
872
- const hasArgsOrDirectives = fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives;
873
- let fieldData = get(data, responseDataPath, null);
874
- const isEntity = this._isFieldEntity(fieldData, fieldTypeInfo);
918
+ const { requestFieldPath, responseDataPath } = keysAndPaths;
919
+ const fieldData = get(entityData, responseDataPath);
920
+ const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
921
+ const cacheability = cacheMetadata.get(requestFieldPath);
875
922
 
876
- if (!isEntity && hasArgsOrDirectives) {
877
- unset(data, responseDataPath);
923
+ if (isUndefined(fieldData) || !fieldTypeInfo || !cacheability) {
924
+ return;
878
925
  }
879
926
 
880
- if (isEntity) {
881
- const fieldTypeName = fieldTypeInfo.isEntity ? fieldTypeInfo.typeName : fieldData.__typename;
882
- const entityDataKey = `${fieldTypeName}::${fieldData[this._typeIDKey]}`;
883
- const result = await this._checkCacheEntry(DATA_ENTITIES, entityDataKey, options, context);
884
-
885
- if (result) {
886
- fieldData = this._mergeObjects(result.entry, fieldData);
887
- }
927
+ const promises: Promise<void>[] = [];
888
928
 
889
- await this._setCacheEntry(
890
- DATA_ENTITIES,
891
- entityDataKey,
892
- fieldData,
893
- { cacheHeaders: { cacheControl: cacheability.printCacheControl() }, tag: options.tag },
929
+ promises.push(
930
+ this._setRequestFieldPathCacheEntry(
931
+ field,
932
+ keysAndPaths,
933
+ { cacheability, data: requestFieldPathData, fieldTypeInfo },
894
934
  options,
895
935
  context,
896
- );
936
+ ),
937
+ );
938
+
939
+ const isEntity = this._isFieldEntity(fieldData, fieldTypeInfo);
897
940
 
898
- set(data, responseDataPath, { __cacheKey: `${DATA_ENTITIES}::${entityDataKey}` });
941
+ if (!isEntity && fieldTypeInfo.hasArguments) {
942
+ unset(entityData, responseDataPath);
943
+ }
944
+
945
+ if (isEntity) {
946
+ promises.push(
947
+ this._setEntityCacheEntry(keysAndPaths, { cacheability, data: entityData, fieldTypeInfo }, options, context),
948
+ );
899
949
  }
950
+
951
+ await Promise.all(promises);
900
952
  }
901
953
 
902
- private async _setDataEntityData(
903
- cachedFieldData: CachedFieldData,
904
- { possibleTypes, typeIDValue, typeName }: FieldTypeInfo,
954
+ private async _setEntityCacheEntry(
955
+ { responseDataPath }: KeysAndPaths,
956
+ { cacheability, data, fieldTypeInfo }: DataForCachingEntry,
905
957
  options: RequestOptions,
906
- context: RequestContext,
907
- ): Promise<void> {
908
- const requestFieldPathDataIDValue = isPlainObject(cachedFieldData.requestFieldPathData)
909
- ? cachedFieldData.requestFieldPathData[this._typeIDKey]
910
- : undefined;
911
-
912
- const validTypeIDValue = typeIDValue || requestFieldPathDataIDValue;
913
- if (!validTypeIDValue) return;
958
+ context: CacheManagerContext,
959
+ ) {
960
+ let fieldData = get(data, responseDataPath);
961
+ const fieldTypeName = fieldTypeInfo.isEntity ? fieldTypeInfo.typeName : fieldData.__typename;
962
+ const entityDataKey = `${fieldTypeName}::${fieldData[this._typeIDKey]}`;
963
+ const result = await this._checkCacheEntry(DATA_ENTITIES, entityDataKey, options, context);
914
964
 
915
- const typeNames = [...possibleTypes.map(type => type.typeName), typeName];
965
+ if (result) {
966
+ fieldData = this._mergeObjects(result.entry, fieldData);
967
+ }
916
968
 
917
- const checkResults = await Promise.all(
918
- typeNames.map(name => this._checkCacheEntry(DATA_ENTITIES, `${name}::${validTypeIDValue}`, options, context)),
969
+ await this._setCacheEntry(
970
+ DATA_ENTITIES,
971
+ entityDataKey,
972
+ fieldData,
973
+ { cacheHeaders: { cacheControl: cacheability.printCacheControl() }, tag: options.tag },
974
+ options,
975
+ context,
919
976
  );
920
977
 
921
- const validResults = checkResults.filter(result => !!result) as CheckCacheEntryResult[];
922
- let validResult: CheckCacheEntryResult | undefined;
923
-
924
- if (validResults.length === 1) {
925
- validResult = validResults[0];
926
- } else if (validResults.length > 1) {
927
- validResults.sort(({ cacheability: a }, { cacheability: b }) => a.metadata.ttl - b.metadata.ttl);
928
-
929
- validResult = {
930
- cacheability: validResults[0].cacheability,
931
- entry: validResults.reduce((obj, { entry }) => this._mergeObjects(obj, entry), {}),
932
- };
933
- }
934
-
935
- if (validResult) {
936
- const { cacheability, entry } = validResult;
937
- if (cacheability && !cachedFieldData.cacheability) cachedFieldData.cacheability = cacheability;
938
- if (entry) cachedFieldData.dataEntityData = entry;
939
- }
978
+ set(data, responseDataPath, { __cacheKey: `${DATA_ENTITIES}::${entityDataKey}` });
940
979
  }
941
980
 
942
981
  private _setFieldCacheability(
@@ -944,74 +983,52 @@ export class CacheManager implements CacheManagerDef {
944
983
  ancestorKeysAndPaths: AncestorKeysAndPaths,
945
984
  { cacheMetadata, data }: ResponseData,
946
985
  options: RequestOptions,
947
- context: RequestContext,
986
+ context: CacheManagerContext,
948
987
  ): void {
949
988
  const { requestFieldPath: ancestorRequestFieldPath } = ancestorKeysAndPaths;
950
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, ancestorKeysAndPaths);
989
+ const keysAndPaths = buildFieldKeysAndPaths(field, ancestorKeysAndPaths, context);
951
990
  const { requestFieldPath, responseDataPath } = keysAndPaths;
952
- const fieldData = get(data, responseDataPath, null);
953
- if (!isObjectLike(fieldData)) return;
954
-
955
- const objectLikeFieldData = fieldData as PlainObjectMap | any[];
956
- this._setFieldTypeCacheDirective(cacheMetadata, { ancestorRequestFieldPath, requestFieldPath }, context);
957
-
958
- iterateChildFields(
959
- field,
960
- objectLikeFieldData,
961
- (childField: FieldNode, _typeName: string | undefined, childIndex?: number) => {
962
- this._setFieldCacheability(
963
- childField,
964
- { index: childIndex, requestFieldPath, responseDataPath },
965
- { cacheMetadata, data },
966
- options,
967
- context,
968
- );
969
- },
970
- );
971
- }
972
-
973
- private async _setFieldDataEntityAndRequestFieldPathCacheEntry(
974
- field: FieldNode,
975
- keysAndPaths: KeysAndPaths,
976
- { cacheMetadata, dataEntityData, requestFieldPathData }: ResponseDataForCaching,
977
- options: RequestOptions,
978
- context: RequestContext,
979
- ) {
980
- const { requestFieldPath } = keysAndPaths;
991
+ const fieldData = get(data, responseDataPath);
981
992
  const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
982
- const cacheability = cacheMetadata.get(requestFieldPath);
983
- if (!fieldTypeInfo || !cacheability) return;
984
993
 
985
- const promises: Promise<void>[] = [];
986
-
987
- promises.push(
988
- this._setRequestFieldPathCacheEntry(
989
- field,
990
- keysAndPaths,
991
- { cacheability, data: requestFieldPathData, fieldTypeInfo },
992
- options,
993
- context,
994
- ),
995
- );
994
+ if (!isObjectLike(fieldData) && !fieldTypeInfo?.hasDirectives) {
995
+ return;
996
+ }
996
997
 
997
- promises.push(
998
- this._setDataEntityCacheEntry(
999
- keysAndPaths,
1000
- { cacheability, data: dataEntityData, fieldTypeInfo },
1001
- options,
1002
- context,
1003
- ),
1004
- );
998
+ this._setFieldTypeCacheDirective(cacheMetadata, { ancestorRequestFieldPath, requestFieldPath }, context);
1005
999
 
1006
- await Promise.all(promises);
1000
+ if (isObjectLike(fieldData)) {
1001
+ iterateChildFields(
1002
+ field,
1003
+ fieldData as PlainObjectMap | any[],
1004
+ context.fragmentDefinitions,
1005
+ (
1006
+ childField: FieldNode,
1007
+ _typeName: string | undefined,
1008
+ _fragmentKind: string | undefined,
1009
+ _fragmentName: string | undefined,
1010
+ childIndex?: number,
1011
+ ) => {
1012
+ this._setFieldCacheability(
1013
+ childField,
1014
+ { index: childIndex, requestFieldPath, responseDataPath },
1015
+ { cacheMetadata, data },
1016
+ options,
1017
+ context,
1018
+ );
1019
+ },
1020
+ );
1021
+ }
1007
1022
  }
1008
1023
 
1009
1024
  private _setFieldTypeCacheDirective(
1010
1025
  cacheMetadata: CacheMetadata,
1011
1026
  { ancestorRequestFieldPath, requestFieldPath }: { ancestorRequestFieldPath?: string; requestFieldPath: string },
1012
- { fieldTypeMap, operation }: RequestContext,
1027
+ { fieldTypeMap, operation }: CacheManagerContext,
1013
1028
  ): void {
1014
- if (cacheMetadata.has(requestFieldPath)) return;
1029
+ if (cacheMetadata.has(requestFieldPath)) {
1030
+ return;
1031
+ }
1015
1032
 
1016
1033
  const fieldTypeInfo = fieldTypeMap.get(requestFieldPath);
1017
1034
 
@@ -1033,7 +1050,7 @@ export class CacheManager implements CacheManagerDef {
1033
1050
  hash: string,
1034
1051
  partialQueryResponse: PartialQueryResponse,
1035
1052
  _options: RequestOptions,
1036
- _context: RequestContext,
1053
+ _context: CacheManagerContext,
1037
1054
  ): Promise<void> {
1038
1055
  this._partialQueryResponses.set(hash, partialQueryResponse);
1039
1056
  }
@@ -1042,7 +1059,7 @@ export class CacheManager implements CacheManagerDef {
1042
1059
  hash: string,
1043
1060
  { cacheMetadata, data }: ResponseData,
1044
1061
  options: RequestOptions,
1045
- context: RequestContext,
1062
+ context: CacheManagerContext,
1046
1063
  ): Promise<void> {
1047
1064
  const dehydratedCacheMetadata = dehydrateCacheMetadata(cacheMetadata);
1048
1065
  const cacheControl = CacheManager._getOperationCacheControl(cacheMetadata, context.operation);
@@ -1059,19 +1076,24 @@ export class CacheManager implements CacheManagerDef {
1059
1076
 
1060
1077
  private async _setRequestFieldPathCacheEntry(
1061
1078
  field: FieldNode,
1062
- { hashedRequestFieldCacheKey, responseDataPath }: KeysAndPaths,
1079
+ keysAndPaths: KeysAndPaths,
1063
1080
  { cacheability, data, fieldTypeInfo }: DataForCachingEntry,
1064
1081
  options: RequestOptions,
1065
- context: RequestContext,
1082
+ context: CacheManagerContext,
1066
1083
  ): Promise<void> {
1067
- const hasArgsOrDirectives = fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives;
1068
- let fieldData = get(data, responseDataPath, null);
1084
+ const { hashedRequestFieldCacheKey, responseDataPath } = keysAndPaths;
1085
+ let fieldData = get(data, responseDataPath);
1069
1086
  const isEntity = this._isFieldEntity(fieldData, fieldTypeInfo);
1087
+ const hasArgsOrDirectives = fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives;
1070
1088
 
1071
1089
  if (context.operation === QUERY && (isEntity || hasArgsOrDirectives)) {
1090
+ if (isPlainObject(fieldData) && field.selectionSet?.selections) {
1091
+ fieldData = filterOutPropsWithArgsOrDirectives(fieldData, field.selectionSet.selections, keysAndPaths, context);
1092
+ }
1093
+
1072
1094
  const result = await this._checkCacheEntry(REQUEST_FIELD_PATHS, hashedRequestFieldCacheKey, options, context);
1073
1095
 
1074
- if (result) {
1096
+ if (result && isObjectLike(fieldData)) {
1075
1097
  fieldData = this._mergeObjects(result.entry, fieldData);
1076
1098
  }
1077
1099
 
@@ -1093,21 +1115,6 @@ export class CacheManager implements CacheManagerDef {
1093
1115
  }
1094
1116
  }
1095
1117
  }
1096
-
1097
- private async _setRequestFieldPathData(
1098
- cachedFieldData: CachedFieldData,
1099
- hash: string,
1100
- options: RequestOptions,
1101
- context: RequestContext,
1102
- ): Promise<void> {
1103
- const checkResult = await this._checkCacheEntry(REQUEST_FIELD_PATHS, hash, options, context);
1104
-
1105
- if (checkResult) {
1106
- const { cacheability, entry } = checkResult;
1107
- if (cacheability) cachedFieldData.cacheability = cacheability;
1108
- if (entry) cachedFieldData.requestFieldPathData = entry;
1109
- }
1110
- }
1111
1118
  }
1112
1119
 
1113
1120
  export default function init(userOptions: UserOptions): CacheManagerInit {