@graphql-box/cache-manager 2.1.4 → 2.3.1

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 +24 -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 +495 -488
  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 +12 -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 +492 -486
  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 +11 -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 +15 -22
  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 +17466 -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 +10 -0
  107. package/src/helpers/validTypeIDValue.ts +11 -0
  108. package/src/index.test.ts +179 -3
  109. package/src/main/index.ts +540 -524
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,67 +99,33 @@ 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;
205
114
  }
206
115
 
207
- private static _getResponseCacheMetadata(
208
- cacheMetadata: CacheMetadata,
209
- partialQueryResponse?: PartialQueryResponse,
210
- ): CacheMetadata {
211
- if (!partialQueryResponse) return cacheMetadata;
212
-
213
- return new Map([...partialQueryResponse.cacheMetadata, ...cacheMetadata]);
214
- }
215
-
216
- private static _isDataEntity(fieldTypeInfo?: FieldTypeInfo): boolean {
217
- if (!fieldTypeInfo) return false;
116
+ private static _isNodeEntity(fieldTypeInfo?: FieldTypeInfo): boolean {
117
+ if (!fieldTypeInfo) {
118
+ return false;
119
+ }
218
120
 
219
121
  const { isEntity, possibleTypes } = fieldTypeInfo;
220
122
  return isEntity || possibleTypes.some(type => !!type.isEntity);
221
123
  }
222
124
 
223
- private static _isRequestFieldPath(fieldTypeInfo?: FieldTypeInfo): boolean {
125
+ private static _isNodeRequestFieldPath(fieldTypeInfo?: FieldTypeInfo): boolean {
224
126
  return (
225
127
  !!fieldTypeInfo &&
226
- (this._isDataEntity(fieldTypeInfo) || fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives)
128
+ (this._isNodeEntity(fieldTypeInfo) || fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives)
227
129
  );
228
130
  }
229
131
 
@@ -232,6 +134,17 @@ export class CacheManager implements CacheManagerDef {
232
134
  return !noCache && cacheability.checkTTL();
233
135
  }
234
136
 
137
+ private static _mergeResponseCacheMetadata(
138
+ cacheMetadata: CacheMetadata,
139
+ partialQueryResponse?: PartialQueryResponse,
140
+ ): CacheMetadata {
141
+ if (!partialQueryResponse) {
142
+ return cacheMetadata;
143
+ }
144
+
145
+ return new Map([...partialQueryResponse.cacheMetadata, ...cacheMetadata]);
146
+ }
147
+
235
148
  private static _setCachedData(
236
149
  requestData: PlainObjectMap,
237
150
  { data }: MergedCachedFieldData,
@@ -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,34 +322,59 @@ 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, path } = 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) {
393
- dataCaching.push(
394
- this._setQueryResponseCacheEntry(updatedRequestData.hash, { cacheMetadata, data }, options, context),
395
- );
342
+ if (cacheManagerContext.queryFiltered) {
343
+ if (!(rawResponseData.hasNext || rawResponseData.path)) {
344
+ dataCaching.push(
345
+ this._setQueryResponseCacheEntry(
346
+ updatedRequestData.hash,
347
+ { cacheMetadata, data },
348
+ options,
349
+ cacheManagerContext,
350
+ ),
351
+ );
352
+ }
396
353
 
397
- partialQueryResponse = this._getPartialQueryResponse(requestData.hash);
354
+ if (!rawResponseData.path) {
355
+ partialQueryResponse = this._getPartialQueryResponse(requestData.hash);
356
+ }
398
357
  }
399
358
 
400
- const responseCacheMetadata = CacheManager._getResponseCacheMetadata(cacheMetadata, partialQueryResponse);
401
- const responseData = this._getResponseData(data, partialQueryResponse);
359
+ const responseCacheMetadata = CacheManager._mergeResponseCacheMetadata(cacheMetadata, partialQueryResponse);
360
+ const responseData = this._mergeResponseData(data, partialQueryResponse);
402
361
 
403
- dataCaching.push(
404
- this._setQueryResponseCacheEntry(
405
- requestData.hash,
406
- { cacheMetadata: responseCacheMetadata, data: responseData },
407
- options,
408
- context,
409
- ),
410
- );
362
+ if (!(rawResponseData.hasNext || rawResponseData.path)) {
363
+ dataCaching.push(
364
+ this._setQueryResponseCacheEntry(
365
+ requestData.hash,
366
+ { cacheMetadata: responseCacheMetadata, data: responseData },
367
+ options,
368
+ cacheManagerContext,
369
+ ),
370
+ );
371
+ }
411
372
 
412
- if (options.awaitDataCaching) await Promise.all(dataCaching);
373
+ if (options.awaitDataCaching) {
374
+ await Promise.all(dataCaching);
375
+ }
413
376
 
414
- return { cacheMetadata: responseCacheMetadata, data: responseData };
377
+ return { cacheMetadata: responseCacheMetadata, data: responseData, hasNext, path };
415
378
  }
416
379
 
417
380
  public async resolveRequest(
@@ -420,82 +383,141 @@ export class CacheManager implements CacheManagerDef {
420
383
  options: RequestOptions,
421
384
  context: RequestContext,
422
385
  ): Promise<ResponseData> {
423
- return this._resolveRequest(requestData, rawResponseData, options, context);
386
+ const cacheManagerContext: CacheManagerContext = {
387
+ ...context,
388
+ fragmentDefinitions: getFragmentDefinitions(requestData.ast),
389
+ typeIDKey: this._typeIDKey,
390
+ };
391
+
392
+ return this._resolveRequest(requestData, rawResponseData, options, cacheManagerContext);
424
393
  }
425
394
 
426
- private async _analyzeField(
427
- field: FieldNode,
395
+ private async _analyzeFieldNode(
396
+ fieldNode: FieldNode,
428
397
  cachedAncestorFieldData: CachedAncestorFieldData,
429
398
  cachedResponseData: CachedResponseData,
430
399
  options: RequestOptions,
431
- context: RequestContext,
400
+ context: CacheManagerContext,
432
401
  ): Promise<void> {
433
- if (hasChildFields(field)) {
434
- await this._analyzeParentField(field, cachedAncestorFieldData, cachedResponseData, options, context);
402
+ if (hasChildFields(fieldNode)) {
403
+ await this._analyzeParentFieldNode(fieldNode, cachedAncestorFieldData, cachedResponseData, options, context);
435
404
  } else {
436
- await CacheManager._analyzeLeafField(field, cachedAncestorFieldData, cachedResponseData, options, context);
405
+ await this._analyzeLeafFieldNode(fieldNode, cachedAncestorFieldData, cachedResponseData, options, context);
437
406
  }
438
407
  }
439
408
 
440
- private async _analyzeParentField(
441
- field: FieldNode,
409
+ private async _analyzeLeafFieldNode(
410
+ fieldNode: FieldNode,
442
411
  cachedAncestorFieldData: CachedAncestorFieldData,
443
412
  cachedResponseData: CachedResponseData,
444
413
  options: RequestOptions,
445
- context: RequestContext,
414
+ context: CacheManagerContext,
446
415
  ): Promise<void> {
447
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, cachedAncestorFieldData);
448
- const { hashedRequestFieldCacheKey, propNameOrIndex, requestFieldCacheKey, requestFieldPath } = keysAndPaths;
416
+ const keysAndPaths = buildFieldKeysAndPaths(fieldNode, cachedAncestorFieldData, context);
417
+ const { hashedRequestFieldCacheKey, propNameOrIndex, requestFieldPath } = keysAndPaths;
449
418
  const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
419
+ const { entityData, fragmentKind, fragmentName, requestFieldPathData, typeName } = cachedAncestorFieldData;
450
420
 
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),
421
+ const typeNamesAndKind = {
422
+ dataTypeName: entityData?.__typename || requestFieldPathData?.__typename,
423
+ fieldTypeName: typeName,
424
+ fragmentKind,
425
+ fragmentName,
460
426
  };
461
427
 
462
- if (CacheManager._isRequestFieldPath(fieldTypeInfo)) {
463
- await this._setRequestFieldPathData(cachedFieldData, hashedRequestFieldCacheKey, options, context);
464
- }
428
+ if (CacheManager._isNodeRequestFieldPath(fieldTypeInfo)) {
429
+ const { cacheability, entry } = await this._retrieveCachedRequestFieldPathData(
430
+ hashedRequestFieldCacheKey,
431
+ options,
432
+ context,
433
+ );
434
+
435
+ CacheManager._setCachedResponseData(
436
+ { cacheability, data: entry },
437
+ cachedResponseData,
438
+ keysAndPaths,
439
+ typeNamesAndKind,
440
+ options,
441
+ context,
442
+ );
443
+ } else {
444
+ const cachedFieldData =
445
+ CacheManager._getFieldDataFromAncestor(entityData, propNameOrIndex) ||
446
+ CacheManager._getFieldDataFromAncestor(requestFieldPathData, propNameOrIndex);
447
+
448
+ CacheManager._setFieldPathChecklist(
449
+ cachedResponseData.fieldPathChecklist,
450
+ { data: cachedFieldData },
451
+ requestFieldPath,
452
+ typeNamesAndKind,
453
+ );
465
454
 
466
- if (CacheManager._isDataEntity(fieldTypeInfo)) {
467
- await this._setDataEntityData(cachedFieldData, fieldTypeInfo as FieldTypeInfo, options, context);
455
+ CacheManager._setCachedData(cachedResponseData.data, { data: cachedFieldData }, propNameOrIndex);
468
456
  }
457
+ }
458
+
459
+ private async _analyzeParentFieldNode(
460
+ fieldNode: FieldNode,
461
+ cachedAncestorFieldData: CachedAncestorFieldData,
462
+ cachedResponseData: CachedResponseData,
463
+ options: RequestOptions,
464
+ context: CacheManagerContext,
465
+ ): Promise<void> {
466
+ const keysAndPaths = buildFieldKeysAndPaths(fieldNode, cachedAncestorFieldData, context);
467
+ const { propNameOrIndex, requestFieldCacheKey, requestFieldPath } = keysAndPaths;
468
+ const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath) as FieldTypeInfo;
469
469
 
470
- const { cacheability, dataEntityData, requestFieldPathData } = cachedFieldData;
470
+ const { cacheability, data, entityData, requestFieldPathData } = await this._retrieveCachedParentNodeData(
471
+ cachedAncestorFieldData,
472
+ keysAndPaths,
473
+ fieldTypeInfo,
474
+ options,
475
+ context,
476
+ );
471
477
 
472
- const data =
473
- !isUndefined(requestFieldPathData) || !isUndefined(dataEntityData)
474
- ? this._mergeObjects(requestFieldPathData, dataEntityData)
475
- : undefined;
478
+ const { fragmentKind, fragmentName, typeName } = cachedAncestorFieldData;
476
479
 
477
480
  CacheManager._setCachedResponseData(
478
481
  { cacheability, data },
479
482
  cachedResponseData,
480
483
  keysAndPaths,
481
- { dataTypeName: get(data, TYPE_NAME_KEY), fieldTypeName: typeName },
484
+ { dataTypeName: get(data, TYPE_NAME_KEY), fieldTypeName: typeName, fragmentKind, fragmentName },
482
485
  options,
483
486
  context,
484
487
  );
485
488
 
486
- if (!isObjectLike(data)) return;
489
+ if (!isObjectLike(data)) {
490
+ return;
491
+ }
487
492
 
488
493
  const objectLikeData = data as PlainObjectMap | any[];
489
494
  const promises: Promise<void>[] = [];
490
495
 
491
496
  iterateChildFields(
492
- field,
497
+ fieldNode,
493
498
  objectLikeData,
494
- (childField: FieldNode, childTypeName: string | undefined, childIndex?: number) => {
499
+ context.fragmentDefinitions,
500
+ (
501
+ childField: FieldNode,
502
+ childTypeName: string | undefined,
503
+ childFragmentKind: string | undefined,
504
+ childFragmentName: string | undefined,
505
+ childIndex?: number,
506
+ ) => {
495
507
  promises.push(
496
- this._analyzeField(
508
+ this._analyzeFieldNode(
497
509
  childField,
498
- { index: childIndex, requestFieldCacheKey, requestFieldPath, typeName: childTypeName, ...cachedFieldData },
510
+ {
511
+ cacheability,
512
+ entityData,
513
+ fragmentKind: childFragmentKind,
514
+ fragmentName: childFragmentName,
515
+ index: childIndex,
516
+ requestFieldCacheKey,
517
+ requestFieldPath,
518
+ requestFieldPathData,
519
+ typeName: childTypeName,
520
+ },
499
521
  { ...cachedResponseData, data: cachedResponseData.data[propNameOrIndex] },
500
522
  options,
501
523
  context,
@@ -511,12 +533,15 @@ export class CacheManager implements CacheManagerDef {
511
533
  { ast }: RequestData,
512
534
  { data, ...otherProps }: RawResponseDataWithMaybeCacheMetadata,
513
535
  options: RequestOptions,
514
- context: RequestContext,
536
+ context: CacheManagerContext,
515
537
  ): CacheMetadata {
516
538
  const cacheMetadata = this._createCacheMetadata({ data, ...otherProps }, context);
517
539
  const queryNode = getOperationDefinitions(ast, context.operation)[0];
518
540
  const fieldsAndTypeNames = getChildFields(queryNode);
519
- if (!fieldsAndTypeNames) return cacheMetadata;
541
+
542
+ if (!fieldsAndTypeNames) {
543
+ return cacheMetadata;
544
+ }
520
545
 
521
546
  fieldsAndTypeNames.forEach(({ fieldNode }) =>
522
547
  this._setFieldCacheability(
@@ -535,16 +560,20 @@ export class CacheManager implements CacheManagerDef {
535
560
  cacheType: CacheTypes,
536
561
  hash: string,
537
562
  options: RequestOptions,
538
- context: RequestContext,
563
+ context: CacheManagerContext,
539
564
  ): Promise<CheckCacheEntryResult | false> {
540
565
  try {
541
566
  const cacheability = await this._hasCacheEntry(cacheType, hash);
542
567
 
543
- if (!cacheability || !CacheManager._isValid(cacheability)) return false;
568
+ if (!cacheability || !CacheManager._isValid(cacheability)) {
569
+ return false;
570
+ }
544
571
 
545
572
  const entry = await this._getCacheEntry(cacheType, hash, options, context);
546
573
 
547
- if (!entry) return false;
574
+ if (isUndefined(entry)) {
575
+ return false;
576
+ }
548
577
 
549
578
  return { cacheability, entry };
550
579
  } catch (error) {
@@ -554,7 +583,7 @@ export class CacheManager implements CacheManagerDef {
554
583
 
555
584
  private _createCacheMetadata(
556
585
  { _cacheMetadata, headers }: RawResponseDataWithMaybeCacheMetadata,
557
- { operation }: RequestContext,
586
+ { operation }: CacheManagerContext,
558
587
  ): CacheMetadata {
559
588
  const cacheMetadata = new Map();
560
589
 
@@ -573,151 +602,13 @@ export class CacheManager implements CacheManagerDef {
573
602
  return cacheMetadata;
574
603
  }
575
604
 
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
605
  @logCacheQuery()
715
606
  private async _getCacheEntry(
716
607
  cacheType: CacheTypes,
717
608
  hash: string,
718
609
  _options: RequestOptions,
719
- _context: RequestContext,
720
- ): Promise<ResponseData> {
610
+ _context: CacheManagerContext,
611
+ ): Promise<any> {
721
612
  try {
722
613
  return await this._cache.get(`${cacheType}::${hash}`);
723
614
  } catch (errors) {
@@ -731,12 +622,6 @@ export class CacheManager implements CacheManagerDef {
731
622
  return partialQueryResponse;
732
623
  }
733
624
 
734
- private _getResponseData(responseData: PlainObjectMap, partialQueryResponse?: PartialQueryResponse): PlainObjectMap {
735
- if (!partialQueryResponse) return responseData;
736
-
737
- return this._mergeObjects(partialQueryResponse.data, responseData);
738
- }
739
-
740
625
  private async _hasCacheEntry(cacheType: CacheTypes, hash: string): Promise<Cacheability | false> {
741
626
  try {
742
627
  return await this._cache.has(`${cacheType}::${hash}`);
@@ -746,9 +631,18 @@ export class CacheManager implements CacheManagerDef {
746
631
  }
747
632
 
748
633
  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;
634
+ if (!get(fieldData, this._typeIDKey, null)) {
635
+ return false;
636
+ }
637
+
638
+ if (isEntity) {
639
+ return true;
640
+ }
641
+
642
+ if (!possibleTypes.length) {
643
+ return false;
644
+ }
645
+
752
646
  return possibleTypes.some(type => type.typeName === fieldData.__typename);
753
647
  }
754
648
 
@@ -758,43 +652,66 @@ export class CacheManager implements CacheManagerDef {
758
652
  });
759
653
  }
760
654
 
761
- private async _parseFieldDataEntityAndRequestFieldPathCacheEntryData(
655
+ private _mergeResponseData(
656
+ responseData: PlainObjectMap,
657
+ partialQueryResponse?: PartialQueryResponse,
658
+ ): PlainObjectMap {
659
+ if (!partialQueryResponse) {
660
+ return responseData;
661
+ }
662
+
663
+ return this._mergeObjects(partialQueryResponse.data, responseData);
664
+ }
665
+
666
+ private async _parseEntityAndRequestFieldPathCacheEntryData(
762
667
  field: FieldNode,
763
668
  ancestorKeysAndPaths: AncestorKeysAndPaths,
764
- { cacheMetadata, dataEntityData, requestFieldPathData }: ResponseDataForCaching,
669
+ { cacheMetadata, entityData, requestFieldPathData }: ResponseDataForCaching,
765
670
  options: RequestOptions,
766
- context: RequestContext,
671
+ context: CacheManagerContext,
767
672
  ): Promise<void> {
768
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, ancestorKeysAndPaths);
673
+ const keysAndPaths = buildFieldKeysAndPaths(field, ancestorKeysAndPaths, context);
769
674
  const { requestFieldCacheKey, requestFieldPath, responseDataPath } = keysAndPaths;
770
- const fieldData = get(requestFieldPathData, responseDataPath, null);
771
- if (!isObjectLike(fieldData)) return;
675
+ const fieldData = get(requestFieldPathData, responseDataPath);
676
+ const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
772
677
 
773
- const objectLikeFieldData = fieldData as PlainObjectMap | any[];
774
- const promises: Promise<void>[] = [];
678
+ if (!isObjectLike(fieldData) && !fieldTypeInfo?.hasDirectives) {
679
+ return;
680
+ }
775
681
 
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
- );
682
+ if (isObjectLike(fieldData)) {
683
+ const promises: Promise<void>[] = [];
791
684
 
792
- await Promise.all(promises);
685
+ iterateChildFields(
686
+ field,
687
+ fieldData as PlainObjectMap | any[],
688
+ context.fragmentDefinitions,
689
+ (
690
+ childField: FieldNode,
691
+ _typeName: string | undefined,
692
+ _fragmentKind: string | undefined,
693
+ _fragmentName: string | undefined,
694
+ childIndex?: number,
695
+ ) => {
696
+ promises.push(
697
+ this._parseEntityAndRequestFieldPathCacheEntryData(
698
+ childField,
699
+ { index: childIndex, requestFieldCacheKey, requestFieldPath, responseDataPath },
700
+ { cacheMetadata, entityData, requestFieldPathData },
701
+ options,
702
+ context,
703
+ ),
704
+ );
705
+ },
706
+ );
707
+
708
+ await Promise.all(promises);
709
+ }
793
710
 
794
- await this._setFieldDataEntityAndRequestFieldPathCacheEntry(
711
+ await this._setEntityAndRequestFieldPathCacheEntry(
795
712
  field,
796
713
  keysAndPaths,
797
- { cacheMetadata, dataEntityData, requestFieldPathData },
714
+ { cacheMetadata, entityData, requestFieldPathData },
798
715
  options,
799
716
  context,
800
717
  );
@@ -804,24 +721,158 @@ export class CacheManager implements CacheManagerDef {
804
721
  requestData: RequestData,
805
722
  rawResponseData: RawResponseDataWithMaybeCacheMetadata,
806
723
  options: RequestOptions,
807
- context: RequestContext,
724
+ context: CacheManagerContext,
808
725
  ): Promise<ResponseData> {
726
+ const normalizedResponseData = rawResponseData.path ? normalizeResponseData(rawResponseData) : rawResponseData;
809
727
  const dataCaching: Promise<void>[] = [];
810
- const cacheMetadata = this._buildCacheMetadata(requestData, rawResponseData, options, context);
811
- const { data } = rawResponseData;
728
+ const cacheMetadata = this._buildCacheMetadata(requestData, normalizedResponseData, options, context);
729
+ const { data, hasNext, path } = normalizedResponseData;
812
730
 
813
731
  dataCaching.push(
814
- this._setDataEntityAndRequestFieldPathCacheEntries(
732
+ this._setEntityAndRequestFieldPathCacheEntries(
815
733
  requestData,
816
- { cacheMetadata, dataEntityData: cloneDeep(data), requestFieldPathData: cloneDeep(data) },
734
+ { cacheMetadata, entityData: cloneDeep(data), requestFieldPathData: cloneDeep(data) },
817
735
  options,
818
736
  context,
819
737
  ),
820
738
  );
821
739
 
822
- if (options.awaitDataCaching) await Promise.all(dataCaching);
740
+ if (options.awaitDataCaching) {
741
+ await Promise.all(dataCaching);
742
+ }
743
+
744
+ return { cacheMetadata, data, hasNext, path };
745
+ }
746
+
747
+ private async _retrieveCachedEntityData(
748
+ validTypeIDValue: string | number,
749
+ { possibleTypes, typeName }: FieldTypeInfo,
750
+ options: RequestOptions,
751
+ context: CacheManagerContext,
752
+ ) {
753
+ const typeNames = [...possibleTypes.map(type => type.typeName), typeName];
754
+
755
+ const checkResults = await Promise.all(
756
+ typeNames.map(name => this._checkCacheEntry(DATA_ENTITIES, `${name}::${validTypeIDValue}`, options, context)),
757
+ );
758
+
759
+ const validResults = checkResults.filter(result => !!result) as CheckCacheEntryResult[];
760
+ let validResult: CheckCacheEntryResult | undefined;
761
+
762
+ if (validResults.length === 1) {
763
+ validResult = validResults[0];
764
+ } else if (validResults.length > 1) {
765
+ validResults.sort(({ cacheability: a }, { cacheability: b }) => a.metadata.ttl - b.metadata.ttl);
766
+
767
+ validResult = {
768
+ cacheability: validResults[0].cacheability,
769
+ entry: validResults.reduce((obj, { entry }) => this._mergeObjects(obj, entry), {}),
770
+ };
771
+ }
772
+
773
+ return (validResult || {}) as Partial<CheckCacheEntryResult>;
774
+ }
775
+
776
+ private async _retrieveCachedParentNodeData(
777
+ { entityData: ancestorEntityData, requestFieldPathData: ancestorRequestFieldPathData }: CachedAncestorFieldData,
778
+ { hashedRequestFieldCacheKey, propNameOrIndex }: KeysAndPaths,
779
+ fieldTypeInfo: FieldTypeInfo,
780
+ options: RequestOptions,
781
+ context: CacheManagerContext,
782
+ ) {
783
+ let entityData = CacheManager._getFieldDataFromAncestor(ancestorEntityData, propNameOrIndex);
784
+ let requestFieldPathData = CacheManager._getFieldDataFromAncestor(ancestorRequestFieldPathData, propNameOrIndex);
785
+ let cacheability: Cacheability | undefined;
786
+
787
+ if (CacheManager._isNodeRequestFieldPath(fieldTypeInfo)) {
788
+ const { cacheability: entryCacheability, entry } = await this._retrieveCachedRequestFieldPathData(
789
+ hashedRequestFieldCacheKey,
790
+ options,
791
+ context,
792
+ );
793
+
794
+ if (entry) {
795
+ requestFieldPathData = this._mergeObjects(requestFieldPathData, entry);
796
+ }
797
+
798
+ if (entryCacheability) {
799
+ cacheability = entryCacheability;
800
+ }
801
+ }
802
+
803
+ const validTypeIDValue = getValidTypeIDValue(requestFieldPathData, fieldTypeInfo, this._typeIDKey);
804
+
805
+ if (CacheManager._isNodeEntity(fieldTypeInfo) && validTypeIDValue) {
806
+ const { cacheability: entryCacheability, entry } = await this._retrieveCachedEntityData(
807
+ validTypeIDValue,
808
+ fieldTypeInfo,
809
+ options,
810
+ context,
811
+ );
812
+
813
+ if (entry) {
814
+ entityData = this._mergeObjects(entityData, entry);
815
+ }
816
+
817
+ if (entryCacheability && (!cacheability || entryCacheability.metadata.ttl > cacheability?.metadata.ttl)) {
818
+ cacheability = entryCacheability;
819
+ }
820
+ }
821
+
822
+ const data =
823
+ !isUndefined(requestFieldPathData) || !isUndefined(entityData)
824
+ ? this._mergeObjects(requestFieldPathData, entityData)
825
+ : entityData ?? requestFieldPathData;
826
+
827
+ return {
828
+ cacheability,
829
+ data,
830
+ entityData,
831
+ requestFieldPathData,
832
+ };
833
+ }
823
834
 
824
- return { cacheMetadata, data };
835
+ private async _retrieveCachedRequestFieldPathData(
836
+ hash: string,
837
+ options: RequestOptions,
838
+ context: CacheManagerContext,
839
+ ) {
840
+ return (this._checkCacheEntry(REQUEST_FIELD_PATHS, hash, options, context) || {}) as Partial<CheckCacheEntryResult>;
841
+ }
842
+
843
+ private async _retrieveCachedResponseData(
844
+ { ast }: RequestData,
845
+ options: RequestOptions,
846
+ context: CacheManagerContext,
847
+ ): Promise<CachedResponseData> {
848
+ const cachedResponseData: CachedResponseData = {
849
+ cacheMetadata: new Map(),
850
+ data: {},
851
+ fieldCount: { missing: 0, total: 0 },
852
+ fieldPathChecklist: new Map(),
853
+ };
854
+
855
+ const queryNode = getOperationDefinitions(ast, context.operation)[0];
856
+ const fieldsAndTypeNames = getChildFields(queryNode);
857
+
858
+ if (!fieldsAndTypeNames) {
859
+ return cachedResponseData;
860
+ }
861
+
862
+ await Promise.all(
863
+ fieldsAndTypeNames.map(({ fieldNode }) =>
864
+ this._analyzeFieldNode(
865
+ fieldNode,
866
+ { requestFieldPath: context.operation },
867
+ cachedResponseData,
868
+ options,
869
+ context,
870
+ ),
871
+ ),
872
+ );
873
+
874
+ cachedResponseData.fieldCount = CacheManager._countFieldPathChecklist(cachedResponseData.fieldPathChecklist);
875
+ return cachedResponseData;
825
876
  }
826
877
 
827
878
  @logCacheEntry()
@@ -831,7 +882,7 @@ export class CacheManager implements CacheManagerDef {
831
882
  value: any,
832
883
  cachemapOptions: CachemapOptions,
833
884
  _options: RequestOptions,
834
- _context: RequestContext,
885
+ _context: CacheManagerContext,
835
886
  ): Promise<void> {
836
887
  try {
837
888
  await this._cache.set(`${cacheType}::${hash}`, cloneDeep(value), cachemapOptions);
@@ -840,19 +891,22 @@ export class CacheManager implements CacheManagerDef {
840
891
  }
841
892
  }
842
893
 
843
- private async _setDataEntityAndRequestFieldPathCacheEntries(
894
+ private async _setEntityAndRequestFieldPathCacheEntries(
844
895
  requestData: RequestData,
845
896
  responseData: ResponseDataForCaching,
846
897
  options: RequestOptions,
847
- context: RequestContext,
898
+ context: CacheManagerContext,
848
899
  ): Promise<void> {
849
900
  const operationNode = getOperationDefinitions(requestData.ast, context.operation)[0];
850
901
  const fieldsAndTypeNames = getChildFields(operationNode);
851
- if (!fieldsAndTypeNames) return;
902
+
903
+ if (!fieldsAndTypeNames) {
904
+ return;
905
+ }
852
906
 
853
907
  await Promise.all(
854
908
  fieldsAndTypeNames.map(({ fieldNode }) => {
855
- return this._parseFieldDataEntityAndRequestFieldPathCacheEntryData(
909
+ return this._parseEntityAndRequestFieldPathCacheEntryData(
856
910
  fieldNode,
857
911
  { requestFieldPath: context.operation },
858
912
  responseData,
@@ -863,80 +917,74 @@ export class CacheManager implements CacheManagerDef {
863
917
  );
864
918
  }
865
919
 
866
- private async _setDataEntityCacheEntry(
867
- { responseDataPath }: KeysAndPaths,
868
- { cacheability, data, fieldTypeInfo }: DataForCachingEntry,
920
+ private async _setEntityAndRequestFieldPathCacheEntry(
921
+ field: FieldNode,
922
+ keysAndPaths: KeysAndPaths,
923
+ { cacheMetadata, entityData, requestFieldPathData }: ResponseDataForCaching,
869
924
  options: RequestOptions,
870
- context: RequestContext,
925
+ context: CacheManagerContext,
871
926
  ) {
872
- const hasArgsOrDirectives = fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives;
873
- let fieldData = get(data, responseDataPath, null);
874
- const isEntity = this._isFieldEntity(fieldData, fieldTypeInfo);
927
+ const { requestFieldPath, responseDataPath } = keysAndPaths;
928
+ const fieldData = get(entityData, responseDataPath);
929
+ const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
930
+ const cacheability = cacheMetadata.get(requestFieldPath);
875
931
 
876
- if (!isEntity && hasArgsOrDirectives) {
877
- unset(data, responseDataPath);
932
+ if (isUndefined(fieldData) || !fieldTypeInfo || !cacheability) {
933
+ return;
878
934
  }
879
935
 
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
- }
936
+ const promises: Promise<void>[] = [];
888
937
 
889
- await this._setCacheEntry(
890
- DATA_ENTITIES,
891
- entityDataKey,
892
- fieldData,
893
- { cacheHeaders: { cacheControl: cacheability.printCacheControl() }, tag: options.tag },
938
+ promises.push(
939
+ this._setRequestFieldPathCacheEntry(
940
+ field,
941
+ keysAndPaths,
942
+ { cacheability, data: requestFieldPathData, fieldTypeInfo },
894
943
  options,
895
944
  context,
896
- );
945
+ ),
946
+ );
947
+
948
+ const isEntity = this._isFieldEntity(fieldData, fieldTypeInfo);
897
949
 
898
- set(data, responseDataPath, { __cacheKey: `${DATA_ENTITIES}::${entityDataKey}` });
950
+ if (!isEntity && fieldTypeInfo.hasArguments) {
951
+ unset(entityData, responseDataPath);
899
952
  }
953
+
954
+ if (isEntity) {
955
+ promises.push(
956
+ this._setEntityCacheEntry(keysAndPaths, { cacheability, data: entityData, fieldTypeInfo }, options, context),
957
+ );
958
+ }
959
+
960
+ await Promise.all(promises);
900
961
  }
901
962
 
902
- private async _setDataEntityData(
903
- cachedFieldData: CachedFieldData,
904
- { possibleTypes, typeIDValue, typeName }: FieldTypeInfo,
963
+ private async _setEntityCacheEntry(
964
+ { responseDataPath }: KeysAndPaths,
965
+ { cacheability, data, fieldTypeInfo }: DataForCachingEntry,
905
966
  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;
967
+ context: CacheManagerContext,
968
+ ) {
969
+ let fieldData = get(data, responseDataPath);
970
+ const fieldTypeName = fieldTypeInfo.isEntity ? fieldTypeInfo.typeName : fieldData.__typename;
971
+ const entityDataKey = `${fieldTypeName}::${fieldData[this._typeIDKey]}`;
972
+ const result = await this._checkCacheEntry(DATA_ENTITIES, entityDataKey, options, context);
914
973
 
915
- const typeNames = [...possibleTypes.map(type => type.typeName), typeName];
974
+ if (result) {
975
+ fieldData = this._mergeObjects(result.entry, fieldData);
976
+ }
916
977
 
917
- const checkResults = await Promise.all(
918
- typeNames.map(name => this._checkCacheEntry(DATA_ENTITIES, `${name}::${validTypeIDValue}`, options, context)),
978
+ await this._setCacheEntry(
979
+ DATA_ENTITIES,
980
+ entityDataKey,
981
+ fieldData,
982
+ { cacheHeaders: { cacheControl: cacheability.printCacheControl() }, tag: options.tag },
983
+ options,
984
+ context,
919
985
  );
920
986
 
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
- }
987
+ set(data, responseDataPath, { __cacheKey: `${DATA_ENTITIES}::${entityDataKey}` });
940
988
  }
941
989
 
942
990
  private _setFieldCacheability(
@@ -944,74 +992,52 @@ export class CacheManager implements CacheManagerDef {
944
992
  ancestorKeysAndPaths: AncestorKeysAndPaths,
945
993
  { cacheMetadata, data }: ResponseData,
946
994
  options: RequestOptions,
947
- context: RequestContext,
995
+ context: CacheManagerContext,
948
996
  ): void {
949
997
  const { requestFieldPath: ancestorRequestFieldPath } = ancestorKeysAndPaths;
950
- const keysAndPaths = CacheManager._getFieldKeysAndPaths(field, ancestorKeysAndPaths);
998
+ const keysAndPaths = buildFieldKeysAndPaths(field, ancestorKeysAndPaths, context);
951
999
  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;
1000
+ const fieldData = get(data, responseDataPath);
981
1001
  const fieldTypeInfo = context.fieldTypeMap.get(requestFieldPath);
982
- const cacheability = cacheMetadata.get(requestFieldPath);
983
- if (!fieldTypeInfo || !cacheability) return;
984
1002
 
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
- );
1003
+ if (!isObjectLike(fieldData) && !fieldTypeInfo?.hasDirectives) {
1004
+ return;
1005
+ }
996
1006
 
997
- promises.push(
998
- this._setDataEntityCacheEntry(
999
- keysAndPaths,
1000
- { cacheability, data: dataEntityData, fieldTypeInfo },
1001
- options,
1002
- context,
1003
- ),
1004
- );
1007
+ this._setFieldTypeCacheDirective(cacheMetadata, { ancestorRequestFieldPath, requestFieldPath }, context);
1005
1008
 
1006
- await Promise.all(promises);
1009
+ if (isObjectLike(fieldData)) {
1010
+ iterateChildFields(
1011
+ field,
1012
+ fieldData as PlainObjectMap | any[],
1013
+ context.fragmentDefinitions,
1014
+ (
1015
+ childField: FieldNode,
1016
+ _typeName: string | undefined,
1017
+ _fragmentKind: string | undefined,
1018
+ _fragmentName: string | undefined,
1019
+ childIndex?: number,
1020
+ ) => {
1021
+ this._setFieldCacheability(
1022
+ childField,
1023
+ { index: childIndex, requestFieldPath, responseDataPath },
1024
+ { cacheMetadata, data },
1025
+ options,
1026
+ context,
1027
+ );
1028
+ },
1029
+ );
1030
+ }
1007
1031
  }
1008
1032
 
1009
1033
  private _setFieldTypeCacheDirective(
1010
1034
  cacheMetadata: CacheMetadata,
1011
1035
  { ancestorRequestFieldPath, requestFieldPath }: { ancestorRequestFieldPath?: string; requestFieldPath: string },
1012
- { fieldTypeMap, operation }: RequestContext,
1036
+ { fieldTypeMap, operation }: CacheManagerContext,
1013
1037
  ): void {
1014
- if (cacheMetadata.has(requestFieldPath)) return;
1038
+ if (cacheMetadata.has(requestFieldPath)) {
1039
+ return;
1040
+ }
1015
1041
 
1016
1042
  const fieldTypeInfo = fieldTypeMap.get(requestFieldPath);
1017
1043
 
@@ -1033,7 +1059,7 @@ export class CacheManager implements CacheManagerDef {
1033
1059
  hash: string,
1034
1060
  partialQueryResponse: PartialQueryResponse,
1035
1061
  _options: RequestOptions,
1036
- _context: RequestContext,
1062
+ _context: CacheManagerContext,
1037
1063
  ): Promise<void> {
1038
1064
  this._partialQueryResponses.set(hash, partialQueryResponse);
1039
1065
  }
@@ -1042,7 +1068,7 @@ export class CacheManager implements CacheManagerDef {
1042
1068
  hash: string,
1043
1069
  { cacheMetadata, data }: ResponseData,
1044
1070
  options: RequestOptions,
1045
- context: RequestContext,
1071
+ context: CacheManagerContext,
1046
1072
  ): Promise<void> {
1047
1073
  const dehydratedCacheMetadata = dehydrateCacheMetadata(cacheMetadata);
1048
1074
  const cacheControl = CacheManager._getOperationCacheControl(cacheMetadata, context.operation);
@@ -1059,19 +1085,24 @@ export class CacheManager implements CacheManagerDef {
1059
1085
 
1060
1086
  private async _setRequestFieldPathCacheEntry(
1061
1087
  field: FieldNode,
1062
- { hashedRequestFieldCacheKey, responseDataPath }: KeysAndPaths,
1088
+ keysAndPaths: KeysAndPaths,
1063
1089
  { cacheability, data, fieldTypeInfo }: DataForCachingEntry,
1064
1090
  options: RequestOptions,
1065
- context: RequestContext,
1091
+ context: CacheManagerContext,
1066
1092
  ): Promise<void> {
1067
- const hasArgsOrDirectives = fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives;
1068
- let fieldData = get(data, responseDataPath, null);
1093
+ const { hashedRequestFieldCacheKey, responseDataPath } = keysAndPaths;
1094
+ let fieldData = get(data, responseDataPath);
1069
1095
  const isEntity = this._isFieldEntity(fieldData, fieldTypeInfo);
1096
+ const hasArgsOrDirectives = fieldTypeInfo.hasArguments || fieldTypeInfo.hasDirectives;
1070
1097
 
1071
1098
  if (context.operation === QUERY && (isEntity || hasArgsOrDirectives)) {
1099
+ if (isPlainObject(fieldData) && field.selectionSet?.selections) {
1100
+ fieldData = filterOutPropsWithArgsOrDirectives(fieldData, field.selectionSet.selections, keysAndPaths, context);
1101
+ }
1102
+
1072
1103
  const result = await this._checkCacheEntry(REQUEST_FIELD_PATHS, hashedRequestFieldCacheKey, options, context);
1073
1104
 
1074
- if (result) {
1105
+ if (result && isObjectLike(fieldData)) {
1075
1106
  fieldData = this._mergeObjects(result.entry, fieldData);
1076
1107
  }
1077
1108
 
@@ -1093,21 +1124,6 @@ export class CacheManager implements CacheManagerDef {
1093
1124
  }
1094
1125
  }
1095
1126
  }
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
1127
  }
1112
1128
 
1113
1129
  export default function init(userOptions: UserOptions): CacheManagerInit {