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