@graphql-box/cache-manager 3.4.2 → 4.0.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.
@@ -35193,6 +35193,137 @@ Object {
35193
35193
  }
35194
35194
  `;
35195
35195
 
35196
+ exports[`@graphql-box/cache-manager >> analyzeQuery >> some matching data >> single type with just ID match >> correct cache data 1`] = `
35197
+ Object {
35198
+ "entries": Array [
35199
+ Array [
35200
+ "queryResponses::fe7ca217a961a692d575fe9973836699",
35201
+ Object {
35202
+ "cacheMetadata": Object {
35203
+ "query": Object {
35204
+ "cacheControl": Object {
35205
+ "maxAge": 1,
35206
+ "public": true,
35207
+ },
35208
+ "ttl": 297475201000,
35209
+ },
35210
+ "query.organization": Object {
35211
+ "cacheControl": Object {
35212
+ "maxAge": 1,
35213
+ "public": true,
35214
+ },
35215
+ "ttl": 297475201000,
35216
+ },
35217
+ },
35218
+ "data": Object {
35219
+ "organization": Object {
35220
+ "description": "We are working to build community through open source technology. NB: members must have two-factor auth.",
35221
+ "email": "",
35222
+ "id": "MDEyOk9yZ2FuaXphdGlvbjY5NjMx",
35223
+ },
35224
+ },
35225
+ },
35226
+ ],
35227
+ Array [
35228
+ "requestFieldPaths::5256db516a2438a7e5488a00413f4809",
35229
+ Object {
35230
+ "description": "We are working to build community through open source technology. NB: members must have two-factor auth.",
35231
+ "email": "",
35232
+ "id": "MDEyOk9yZ2FuaXphdGlvbjY5NjMx",
35233
+ },
35234
+ ],
35235
+ Array [
35236
+ "dataEntities::Organization::MDEyOk9yZ2FuaXphdGlvbjY5NjMx",
35237
+ Object {
35238
+ "description": "We are working to build community through open source technology. NB: members must have two-factor auth.",
35239
+ "email": "",
35240
+ "id": "MDEyOk9yZ2FuaXphdGlvbjY5NjMx",
35241
+ },
35242
+ ],
35243
+ ],
35244
+ "metadata": Array [
35245
+ Object {
35246
+ "accessedCount": 1,
35247
+ "added": 297475200000,
35248
+ "cacheability": Cacheability {
35249
+ "metadata": Object {
35250
+ "cacheControl": Object {
35251
+ "maxAge": 1,
35252
+ "public": true,
35253
+ },
35254
+ "etag": undefined,
35255
+ "ttl": 297475201000,
35256
+ },
35257
+ },
35258
+ "key": "requestFieldPaths::5256db516a2438a7e5488a00413f4809",
35259
+ "lastAccessed": 297475200000,
35260
+ "lastUpdated": 297475200000,
35261
+ "size": 456,
35262
+ "tags": Array [],
35263
+ "updatedCount": 0,
35264
+ },
35265
+ Object {
35266
+ "accessedCount": 1,
35267
+ "added": 297475200000,
35268
+ "cacheability": Cacheability {
35269
+ "metadata": Object {
35270
+ "cacheControl": Object {
35271
+ "maxAge": 1,
35272
+ "public": true,
35273
+ },
35274
+ "etag": undefined,
35275
+ "ttl": 297475201000,
35276
+ },
35277
+ },
35278
+ "key": "dataEntities::Organization::MDEyOk9yZ2FuaXphdGlvbjY5NjMx",
35279
+ "lastAccessed": 297475200000,
35280
+ "lastUpdated": 297475200000,
35281
+ "size": 456,
35282
+ "tags": Array [],
35283
+ "updatedCount": 0,
35284
+ },
35285
+ Object {
35286
+ "accessedCount": 0,
35287
+ "added": 297475200000,
35288
+ "cacheability": Cacheability {
35289
+ "metadata": Object {
35290
+ "cacheControl": Object {
35291
+ "maxAge": 1,
35292
+ "public": true,
35293
+ },
35294
+ "etag": undefined,
35295
+ "ttl": 297475201000,
35296
+ },
35297
+ },
35298
+ "key": "queryResponses::fe7ca217a961a692d575fe9973836699",
35299
+ "lastAccessed": 297475200000,
35300
+ "lastUpdated": 297475200000,
35301
+ "size": 984,
35302
+ "tags": Array [],
35303
+ "updatedCount": 0,
35304
+ },
35305
+ ],
35306
+ }
35307
+ `;
35308
+
35309
+ exports[`@graphql-box/cache-manager >> analyzeQuery >> some matching data >> single type with just ID match >> correct partial data 1`] = `Map {}`;
35310
+
35311
+ exports[`@graphql-box/cache-manager >> analyzeQuery >> some matching data >> single type with just ID match >> correct request data 1`] = `
35312
+ Object {
35313
+ "hash": "0f87f523d8cf0a8e383850d601aa3f86",
35314
+ "request": "
35315
+ {
35316
+ organization(login: \\"facebook\\") {
35317
+ login
35318
+ name
35319
+ url
35320
+ id
35321
+ }
35322
+ }
35323
+ ",
35324
+ }
35325
+ `;
35326
+
35196
35327
  exports[`@graphql-box/cache-manager >> resolveQuery >> filtered >> defer >> cascading cache control >> correct cache data 1`] = `
35197
35328
  Object {
35198
35329
  "entries": Array [
package/src/defs/index.ts CHANGED
@@ -42,18 +42,12 @@ export interface UserOptions {
42
42
  * directives used for caching object types.
43
43
  */
44
44
  typeCacheDirectives?: PlainObjectStringMap;
45
- }
46
-
47
- export interface ClientOptions {
48
- typeIDKey: string;
49
- }
50
45
 
51
- export interface ConstructorOptions {
52
- cache: Cachemap;
53
- cascadeCacheControl?: boolean;
54
- fallbackOperationCacheability?: string;
55
- typeCacheDirectives?: PlainObjectStringMap;
56
- typeIDKey: string;
46
+ /**
47
+ * The name of the property thats value is used as the unique
48
+ * identifier for each type in the GraphQL schema.
49
+ */
50
+ typeIDKey?: string;
57
51
  }
58
52
 
59
53
  export interface CacheManagerContext extends RequestContext {
@@ -194,5 +188,3 @@ export interface CacheManagerDef {
194
188
  context: CacheManagerContext,
195
189
  ): Promise<void>;
196
190
  }
197
-
198
- export type CacheManagerInit = (options: ClientOptions) => CacheManagerDef;
@@ -0,0 +1,40 @@
1
+ import { PlainObjectMap } from "@graphql-box/core";
2
+ import { isArray, isPlainObject } from "lodash";
3
+
4
+ const checkValue = (value: any, typeIDKey: string): boolean => {
5
+ if (isArray(value)) {
6
+ return value.reduce((accB: boolean, entry) => {
7
+ if (!accB) {
8
+ return false;
9
+ }
10
+
11
+ return checkValue(entry, typeIDKey);
12
+ }, true);
13
+ }
14
+
15
+ if (isPlainObject(value)) {
16
+ return recursivelyCheckProps(value, typeIDKey);
17
+ }
18
+
19
+ return false;
20
+ };
21
+
22
+ const recursivelyCheckProps = (data: PlainObjectMap, typeIDKey: string): boolean => {
23
+ const keys = Object.keys(data);
24
+
25
+ if (keys.length === 1 && !!data[typeIDKey]) {
26
+ return true;
27
+ }
28
+
29
+ return keys.reduce((accA: boolean, key) => {
30
+ if (!accA) {
31
+ return false;
32
+ }
33
+
34
+ return checkValue(data[key], typeIDKey);
35
+ }, true);
36
+ };
37
+
38
+ export default (data: PlainObjectMap, typeIDKey: string) => {
39
+ return recursivelyCheckProps(data, typeIDKey);
40
+ };
package/src/index.test.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  requestFieldTypeMaps,
18
18
  responses,
19
19
  } from "@graphql-box/test-utils";
20
- import { AnalyzeQueryResult, CacheManager, CacheManagerDef } from ".";
20
+ import CacheManager, { AnalyzeQueryResult, CacheManagerDef } from ".";
21
21
 
22
22
  describe("@graphql-box/cache-manager >>", () => {
23
23
  const realDateNow = Date.now.bind(global.Date);
@@ -1200,6 +1200,60 @@ describe("@graphql-box/cache-manager >>", () => {
1200
1200
  });
1201
1201
  });
1202
1202
 
1203
+ describe("single type with just ID match >>", () => {
1204
+ beforeAll(async () => {
1205
+ analyzeQueryResult = undefined;
1206
+ // @ts-ignore
1207
+ jest.spyOn(CacheManager, "_isValid").mockReturnValue(true);
1208
+
1209
+ cacheManager = new CacheManager({
1210
+ cache: new Cachemap({
1211
+ name: "cachemap",
1212
+ store: map(),
1213
+ type: "someType",
1214
+ }),
1215
+ typeCacheDirectives: {
1216
+ Organization: "public, max-age=1",
1217
+ },
1218
+ typeIDKey: DEFAULT_TYPE_ID_KEY,
1219
+ });
1220
+
1221
+ const requestData = getRequestData(parsedRequests.singleTypeQuerySmallA);
1222
+
1223
+ await cacheManager.cacheQuery(
1224
+ requestData,
1225
+ requestData,
1226
+ responses.singleTypeQuerySmallA,
1227
+ { awaitDataCaching: true },
1228
+ getRequestContext({ fieldTypeMap: requestFieldTypeMaps.singleTypeQuery }),
1229
+ );
1230
+
1231
+ analyzeQueryResult = await cacheManager.analyzeQuery(
1232
+ getRequestData(parsedRequests.singleTypeQuerySmallB),
1233
+ { awaitDataCaching: true },
1234
+ getRequestContext({ fieldTypeMap: requestFieldTypeMaps.singleTypeQuery }),
1235
+ );
1236
+ });
1237
+
1238
+ it("correct request data", () => {
1239
+ const { ast, ...otherProps } = analyzeQueryResult?.updated as RequestData;
1240
+ expect(otherProps).toMatchSnapshot();
1241
+ });
1242
+
1243
+ it("no response data", () => {
1244
+ expect(analyzeQueryResult?.response).toBeUndefined();
1245
+ });
1246
+
1247
+ it("correct cache data", async () => {
1248
+ expect(await cacheManager.cache.export()).toMatchSnapshot();
1249
+ });
1250
+
1251
+ it("correct partial data", () => {
1252
+ // @ts-ignore
1253
+ expect(cacheManager._partialQueryResponses).toMatchSnapshot();
1254
+ });
1255
+ });
1256
+
1203
1257
  describe("nested type with edges >", () => {
1204
1258
  beforeAll(async () => {
1205
1259
  analyzeQueryResult = undefined;
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export * from "./defs";
2
- export { default, CacheManager } from "./main";
2
+ export { default } from "./main";
package/src/main/index.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  CacheTypes,
5
5
  CachemapOptions,
6
6
  DATA_ENTITIES,
7
+ DEFAULT_TYPE_ID_KEY,
7
8
  FieldTypeInfo,
8
9
  PlainObjectMap,
9
10
  PlainObjectStringMap,
@@ -41,12 +42,9 @@ import {
41
42
  AncestorKeysAndPaths,
42
43
  CacheManagerContext,
43
44
  CacheManagerDef,
44
- CacheManagerInit,
45
45
  CachedAncestorFieldData,
46
46
  CachedResponseData,
47
47
  CheckCacheEntryResult,
48
- ClientOptions,
49
- ConstructorOptions,
50
48
  DataForCachingEntry,
51
49
  FieldCount,
52
50
  FieldPathChecklist,
@@ -59,6 +57,7 @@ import {
59
57
  TypeNamesAndKind,
60
58
  UserOptions,
61
59
  } from "../defs";
60
+ import areOnlyPopulatedFieldsTypeIdKeys from "../helpers/areOnlyPopulatedFieldsTypeIdKeys";
62
61
  import deriveOpCacheability from "../helpers/deriveOpCacheability";
63
62
  import filterOutPropsWithArgsOrDirectives from "../helpers/filterOutPropsWithArgsOrDirectives";
64
63
  import filterQuery from "../helpers/filterQuery";
@@ -69,7 +68,7 @@ import mergeResponseDataSets from "../helpers/mergeResponseDataSets";
69
68
  import normalizePatchResponseData from "../helpers/normalizePatchResponseData";
70
69
  import { getValidTypeIDValue } from "../helpers/validTypeIDValue";
71
70
 
72
- export class CacheManager implements CacheManagerDef {
71
+ export default class CacheManager implements CacheManagerDef {
73
72
  private static _countFieldPathChecklist(fieldPathChecklist: FieldPathChecklist): FieldCount {
74
73
  const fieldCount: FieldCount = { missing: 0, total: 0 };
75
74
 
@@ -213,7 +212,7 @@ export class CacheManager implements CacheManagerDef {
213
212
  private _typeCacheDirectives: PlainObjectStringMap;
214
213
  private _typeIDKey: string;
215
214
 
216
- constructor(options: ConstructorOptions) {
215
+ constructor(options: UserOptions) {
217
216
  const errors: TypeError[] = [];
218
217
 
219
218
  if (!options.cache) {
@@ -233,7 +232,7 @@ export class CacheManager implements CacheManagerDef {
233
232
  this._cascadeCacheControl = options.cascadeCacheControl || false;
234
233
  this._fallbackOperationCacheability = options.fallbackOperationCacheability || NO_CACHE;
235
234
  this._typeCacheDirectives = options.typeCacheDirectives || {};
236
- this._typeIDKey = options.typeIDKey;
235
+ this._typeIDKey = options.typeIDKey ?? DEFAULT_TYPE_ID_KEY;
237
236
  }
238
237
 
239
238
  get cache(): Cachemap {
@@ -260,7 +259,10 @@ export class CacheManager implements CacheManagerDef {
260
259
  const cachedResponseData = await this._retrieveCachedResponseData(requestData, options, cacheManagerContext);
261
260
  const { cacheMetadata, data, fieldCount } = cachedResponseData;
262
261
 
263
- if (fieldCount.missing === fieldCount.total) {
262
+ // Second half of check is required for the scenario where the only matching data is
263
+ // the typeIDKey field, i.e. "id", in which case there is no point settings a partial
264
+ // query reponse because we request the typeIDKey field with every request.
265
+ if (fieldCount.missing === fieldCount.total || areOnlyPopulatedFieldsTypeIdKeys(data, this._typeIDKey)) {
264
266
  return { updated: requestData };
265
267
  }
266
268
 
@@ -1179,11 +1181,3 @@ export class CacheManager implements CacheManagerDef {
1179
1181
  }
1180
1182
  }
1181
1183
  }
1182
-
1183
- export default function init(userOptions: UserOptions): CacheManagerInit {
1184
- if (!isPlainObject(userOptions)) {
1185
- throw new TypeError("@graphql-box/cache-manager expected userOptions to be a plain object.");
1186
- }
1187
-
1188
- return (clientOptions: ClientOptions) => new CacheManager({ ...clientOptions, ...userOptions });
1189
- }