@envelop/response-cache 3.0.0-alpha-cadd145.0 → 3.0.0-alpha-0c1aa26.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.
package/README.md CHANGED
@@ -38,35 +38,59 @@ When configuring the `useResponseCache`, you can choose the type of cache:
38
38
  The in-memory LRU cache is used by default.
39
39
 
40
40
  ```ts
41
- import { envelop } from '@envelop/core';
42
- import { useResponseCache } from '@envelop/response-cache';
41
+ import { envelop } from '@envelop/core'
42
+ import { useResponseCache } from '@envelop/response-cache'
43
43
 
44
44
  const getEnveloped = envelop({
45
45
  plugins: [
46
46
  // ... other plugins ...
47
- useResponseCache(),
48
- ],
49
- });
47
+ useResponseCache({
48
+ // use global cache for all operations
49
+ session: () => null
50
+ })
51
+ ]
52
+ })
50
53
  ```
51
54
 
52
55
  Or, you may create the in-memory LRU cache explicitly.
53
56
 
54
57
  ```ts
55
- import { envelop } from '@envelop/core';
56
- import { useResponseCache, createInMemoryCache } from '@envelop/response-cache';
58
+ import { envelop } from '@envelop/core'
59
+ import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
57
60
 
58
- const cache = createInMemoryCache();
61
+ const cache = createInMemoryCache()
59
62
 
60
63
  const getEnveloped = envelop({
61
64
  plugins: [
62
65
  // ... other plugins ...
63
- useResponseCache({ cache }),
64
- ],
65
- });
66
+ useResponseCache({
67
+ cache,
68
+ session: () => null // use global cache for all operations
69
+ })
70
+ ]
71
+ })
66
72
  ```
67
73
 
68
74
  > Note: The in-memory LRU cache is not suitable for serverless deployments. Instead, consider the Redis cache provided by `@envelop/response-cache-redis`.
69
75
 
76
+ ### Cache based on session/user
77
+
78
+ ```ts
79
+ import { envelop } from '@envelop/core'
80
+ import { useResponseCache } from '@envelop/response-cache'
81
+
82
+ const getEnveloped = envelop({
83
+ plugins: [
84
+ // ... other plugins ...
85
+ useResponseCache({
86
+ ttl: 2000,
87
+ // context is the GraphQL context used for execution
88
+ session: context => String(context.user?.id)
89
+ })
90
+ ]
91
+ })
92
+ ```
93
+
70
94
  ### Redis Cache
71
95
 
72
96
  ```bash
@@ -81,28 +105,31 @@ In order to use the Redis cache, you need to:
81
105
  - Create an instance of the Redis Cache and set to the `useResponseCache` plugin options
82
106
 
83
107
  ```ts
84
- import { envelop } from '@envelop/core';
85
- import { useResponseCache } from '@envelop/response-cache';
86
- import { createRedisCache } from '@envelop/response-cache-redis';
108
+ import { envelop } from '@envelop/core'
109
+ import { useResponseCache } from '@envelop/response-cache'
110
+ import { createRedisCache } from '@envelop/response-cache-redis'
87
111
 
88
- import Redis from 'ioredis';
112
+ import Redis from 'ioredis'
89
113
 
90
114
  const redis = new Redis({
91
115
  host: 'my-redis-db.example.com',
92
116
  port: '30652',
93
- password: '1234567890',
94
- });
117
+ password: '1234567890'
118
+ })
95
119
 
96
- const redis = new Redis("rediss://:1234567890@my-redis-db.example.com:30652");
120
+ const redis = new Redis('rediss://:1234567890@my-redis-db.example.com:30652')
97
121
 
98
- const cache = createRedisCache({ redis });
122
+ const cache = createRedisCache({ redis })
99
123
 
100
124
  const getEnveloped = envelop({
101
125
  plugins: [
102
126
  // ... other plugins ...
103
- useResponseCache({ cache }),
104
- ],
105
- });
127
+ useResponseCache({
128
+ cache,
129
+ session: () => null // use global cache for all operations
130
+ })
131
+ ]
132
+ })
106
133
  ```
107
134
 
108
135
  > Note: In the Recipes below, be sure to provide your Redis `cache` instance with `useResponseCache({ cache })`.
@@ -112,17 +139,18 @@ const getEnveloped = envelop({
112
139
  ### Cache with maximum TTL
113
140
 
114
141
  ```ts
115
- import { envelop } from '@envelop/core';
116
- import { useResponseCache } from '@envelop/response-cache';
142
+ import { envelop } from '@envelop/core'
143
+ import { useResponseCache } from '@envelop/response-cache'
117
144
 
118
145
  const getEnveloped = envelop({
119
146
  plugins: [
120
147
  // ... other plugins ...
121
148
  useResponseCache({
122
149
  ttl: 2000, // cached execution results become stale after 2 seconds
123
- }),
124
- ],
125
- });
150
+ session: () => null // use global cache for all operations
151
+ })
152
+ ]
153
+ })
126
154
  ```
127
155
 
128
156
  > Note: Setting `ttl: 0` will disable TTL for all types. You can use that if you wish to disable caching for all type, and then enable caching for specific types using `ttlPerType`.
@@ -130,8 +158,8 @@ const getEnveloped = envelop({
130
158
  ### Cache with custom TTL per object type
131
159
 
132
160
  ```ts
133
- import { envelop } from '@envelop/core';
134
- import { useResponseCache } from '@envelop/response-cache';
161
+ import { envelop } from '@envelop/core'
162
+ import { useResponseCache } from '@envelop/response-cache'
135
163
 
136
164
  const getEnveloped = envelop({
137
165
  plugins: [
@@ -140,18 +168,18 @@ const getEnveloped = envelop({
140
168
  ttl: 2000,
141
169
  ttlPerType: {
142
170
  // cached execution results that contain a `Stock` object become stale after 500ms
143
- Stock: 500,
144
- },
145
- }),
146
- ],
147
- });
171
+ Stock: 500
172
+ }
173
+ })
174
+ ]
175
+ })
148
176
  ```
149
177
 
150
178
  ### Cache with custom TTL per schema coordinate
151
179
 
152
180
  ```ts
153
- import { envelop } from '@envelop/core';
154
- import { useResponseCache } from '@envelop/response-cache';
181
+ import { envelop } from '@envelop/core'
182
+ import { useResponseCache } from '@envelop/response-cache'
155
183
 
156
184
  const getEnveloped = envelop({
157
185
  plugins: [
@@ -160,36 +188,18 @@ const getEnveloped = envelop({
160
188
  ttl: 2000,
161
189
  ttlPerSchemaCoordinate: {
162
190
  // cached execution results that select the `Query.user` field become stale after 100ms
163
- 'Query.rocketCoordinates': 100,
164
- },
165
- }),
166
- ],
167
- });
168
- ```
169
-
170
- ### Cache based on session/user
171
-
172
- ```ts
173
- import { envelop } from '@envelop/core';
174
- import { useResponseCache } from '@envelop/response-cache';
175
-
176
- const getEnveloped = envelop({
177
- plugins: [
178
- // ... other plugins ...
179
- useResponseCache({
180
- ttl: 2000,
181
- // context is the GraphQL context used for execution
182
- session: context => String(context.user?.id),
183
- }),
184
- ],
185
- });
191
+ 'Query.rocketCoordinates': 100
192
+ }
193
+ })
194
+ ]
195
+ })
186
196
  ```
187
197
 
188
198
  ### Disable cache based on session/user
189
199
 
190
200
  ```ts
191
- import { envelop } from '@envelop/core';
192
- import { useResponseCache } from '@envelop/response-cache';
201
+ import { envelop } from '@envelop/core'
202
+ import { useResponseCache } from '@envelop/response-cache'
193
203
 
194
204
  const getEnveloped = envelop({
195
205
  plugins: [
@@ -198,9 +208,10 @@ const getEnveloped = envelop({
198
208
  ttl: 2000,
199
209
  // context is the GraphQL context used for execution
200
210
  enabled: context => context.user?.role !== 'admin',
201
- }),
202
- ],
203
- });
211
+ session: () => null
212
+ })
213
+ ]
214
+ })
204
215
  ```
205
216
 
206
217
  ### Customize if result should be cached
@@ -208,7 +219,7 @@ const getEnveloped = envelop({
208
219
  You can define a custom function used to check if a query operation execution result should be cached.
209
220
 
210
221
  ```ts
211
- type ShouldCacheResultFunction = (params: { result: ExecutionResult }) => Boolean;
222
+ type ShouldCacheResultFunction = (params: { result: ExecutionResult }) => boolean
212
223
  ```
213
224
 
214
225
  This is useful for advanced use-cases. E.g. if you want to
@@ -217,23 +228,24 @@ cache results with certain error types.
217
228
  By default, the `defaultShouldCacheResult` function is used which never caches any query operation execution results that includes any errors (unexpected, EnvelopError, or GraphQLError).
218
229
 
219
230
  ```ts
220
- import { envelop } from '@envelop/core';
221
- import { useResponseCache, ShouldCacheResultFunction } from '@envelop/response-cache';
231
+ import { envelop } from '@envelop/core'
232
+ import { useResponseCache, ShouldCacheResultFunction } from '@envelop/response-cache'
222
233
 
223
234
  export const defaultShouldCacheResult: ShouldCacheResultFunction = (params): Boolean => {
224
235
  // cache any query operation execution result
225
236
  // even if it includes errors
226
- return true;
227
- };
237
+ return true
238
+ }
228
239
 
229
240
  const getEnveloped = envelop({
230
241
  plugins: [
231
242
  // ... other plugins ...
232
243
  useResponseCache({
233
- shouldCacheResult = myCustomShouldCacheResult,
234
- }),
235
- ],
236
- });
244
+ shouldCacheResult: myCustomShouldCacheResult,
245
+ session: () => null
246
+ })
247
+ ]
248
+ })
237
249
  ```
238
250
 
239
251
  ### Cache Introspection query operations
@@ -243,60 +255,63 @@ By default introspection query operations are not cached. In case you want to ca
243
255
  **Infinite caching**
244
256
 
245
257
  ```ts
246
- import { envelop } from '@envelop/core';
247
- import { useResponseCache } from '@envelop/response-cache';
258
+ import { envelop } from '@envelop/core'
259
+ import { useResponseCache } from '@envelop/response-cache'
248
260
 
249
261
  const getEnveloped = envelop({
250
262
  plugins: [
251
263
  // ... other plugins ...
252
264
  useResponseCache({
253
265
  ttlPerSchemaCoordinate: {
254
- 'Query.__schema': undefined, // cache infinitely
266
+ 'Query.__schema': undefined // cache infinitely
255
267
  },
256
- }),
257
- ],
258
- });
268
+ session: () => null
269
+ })
270
+ ]
271
+ })
259
272
  ```
260
273
 
261
274
  **TTL caching**
262
275
 
263
276
  ```ts
264
- import { envelop } from '@envelop/core';
265
- import { useResponseCache } from '@envelop/response-cache';
277
+ import { envelop } from '@envelop/core'
278
+ import { useResponseCache } from '@envelop/response-cache'
266
279
 
267
280
  const getEnveloped = envelop({
268
281
  plugins: [
269
282
  // ... other plugins ...
270
283
  useResponseCache({
271
284
  ttlPerSchemaCoordinate: {
272
- 'Query.__schema': 10_000, // cache for 10 seconds
285
+ 'Query.__schema': 10_000 // cache for 10 seconds
273
286
  },
274
- }),
275
- ],
276
- });
287
+ session: () => null
288
+ })
289
+ ]
290
+ })
277
291
  ```
278
292
 
279
293
  ### Cache with maximum TTL
280
294
 
281
295
  ```ts
282
- import { envelop } from '@envelop/core';
283
- import { useResponseCache } from '@envelop/response-cache';
296
+ import { envelop } from '@envelop/core'
297
+ import { useResponseCache } from '@envelop/response-cache'
284
298
 
285
299
  const getEnveloped = envelop({
286
300
  plugins: [
287
301
  // ... other plugins ...
288
302
  useResponseCache({
289
303
  ttl: 2000, // cached execution results become stale after 2 seconds
290
- }),
291
- ],
292
- });
304
+ session: () => null
305
+ })
306
+ ]
307
+ })
293
308
  ```
294
309
 
295
310
  ### Customize the fields that are used for building the cache ID
296
311
 
297
312
  ```ts
298
- import { envelop } from '@envelop/core';
299
- import { useResponseCache } from '@envelop/response-cache';
313
+ import { envelop } from '@envelop/core'
314
+ import { useResponseCache } from '@envelop/response-cache'
300
315
 
301
316
  const getEnveloped = envelop({
302
317
  plugins: [
@@ -305,16 +320,17 @@ const getEnveloped = envelop({
305
320
  ttl: 2000,
306
321
  // use the `_id` instead of `id` field.
307
322
  idFields: ['_id'],
308
- }),
309
- ],
310
- });
323
+ session: () => null
324
+ })
325
+ ]
326
+ })
311
327
  ```
312
328
 
313
329
  ### Disable automatic cache invalidation via mutations
314
330
 
315
331
  ```ts
316
- import { envelop } from '@envelop/core';
317
- import { useResponseCache } from '@envelop/response-cache';
332
+ import { envelop } from '@envelop/core'
333
+ import { useResponseCache } from '@envelop/response-cache'
318
334
 
319
335
  const getEnveloped = envelop({
320
336
  plugins: [
@@ -323,20 +339,21 @@ const getEnveloped = envelop({
323
339
  ttl: 2000,
324
340
  // some might prefer invalidating based on a database write log
325
341
  invalidateViaMutation: false,
326
- }),
327
- ],
328
- });
342
+ session: () => null
343
+ })
344
+ ]
345
+ })
329
346
  ```
330
347
 
331
348
  ### Invalidate Cache based on custom logic
332
349
 
333
350
  ```ts
334
- import { envelop } from '@envelop/core';
335
- import { useResponseCache, createInMemoryCache } from '@envelop/response-cache';
336
- import { emitter } from './eventEmitter';
351
+ import { envelop } from '@envelop/core'
352
+ import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
353
+ import { emitter } from './eventEmitter'
337
354
 
338
355
  // we create our cache instance, which allows calling all methods on it
339
- const cache = createInMemoryCache();
356
+ const cache = createInMemoryCache()
340
357
 
341
358
  const getEnveloped = envelop({
342
359
  plugins: [
@@ -345,32 +362,33 @@ const getEnveloped = envelop({
345
362
  ttl: 2000,
346
363
  // we pass the cache instance to the request.
347
364
  cache,
348
- }),
349
- ],
350
- });
365
+ session: () => null
366
+ })
367
+ ]
368
+ })
351
369
 
352
370
  emitter.on('invalidate', resource => {
353
371
  cache.invalidate([
354
372
  {
355
373
  typename: resource.type,
356
- id: resource.id,
357
- },
358
- ]);
359
- });
374
+ id: resource.id
375
+ }
376
+ ])
377
+ })
360
378
  ```
361
379
 
362
380
  ### Customize how cache ids are built
363
381
 
364
382
  ```ts
365
- import { envelop } from '@envelop/core';
366
- import { useResponseCache, createInMemoryCache } from '@envelop/response-cache';
367
- import { emitter } from './eventEmitter';
383
+ import { envelop } from '@envelop/core'
384
+ import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
385
+ import { emitter } from './eventEmitter'
368
386
 
369
387
  // we create our cache instance, which allows calling all methods on it
370
388
  const cache = createInMemoryCache({
371
389
  // in relay we have global unique ids, no need to use `typename:id`
372
- makeId: (typename, id) => id ?? typename,
373
- });
390
+ makeId: (typename, id) => id ?? typename
391
+ })
374
392
 
375
393
  const getEnveloped = envelop({
376
394
  plugins: [
@@ -379,9 +397,10 @@ const getEnveloped = envelop({
379
397
  ttl: 2000,
380
398
  // we pass the cache instance to the request.
381
399
  cache,
382
- }),
383
- ],
384
- });
400
+ session: () => null
401
+ })
402
+ ]
403
+ })
385
404
  ```
386
405
 
387
406
  ### Expose cache metadata via extensions
@@ -395,9 +414,10 @@ const getEnveloped = envelop({
395
414
  useResponseCache({
396
415
  ttl: 2000,
397
416
  includeExtensionMetadata: true,
398
- }),
399
- ],
400
- });
417
+ session: () => null
418
+ })
419
+ ]
420
+ })
401
421
  ```
402
422
 
403
423
  This option will attach the following fields to the execution result if set to true (or `process.env["NODE_ENV"]` is `"development"`).
package/cjs/plugin.js CHANGED
@@ -9,7 +9,6 @@ const fast_json_stable_stringify_1 = tslib_1.__importDefault(require("fast-json-
9
9
  const in_memory_cache_js_1 = require("./in-memory-cache.js");
10
10
  const hashSHA256_js_1 = require("./hashSHA256.js");
11
11
  const cache_document_str_js_1 = require("./cache-document-str.js");
12
- const contextSymbol = Symbol('responseCache');
13
12
  /**
14
13
  * Default function used for building the response cache key.
15
14
  * It is exported here for advanced use-cases. E.g. if you want to short circuit and serve responses from the cache on a global level in order to completely by-pass the GraphQL flow.
@@ -41,10 +40,9 @@ const defaultShouldCacheResult = (params) => {
41
40
  return true;
42
41
  };
43
42
  exports.defaultShouldCacheResult = defaultShouldCacheResult;
44
- function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache)(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = exports.defaultBuildResponseCacheKey, getDocumentString = cache_document_str_js_1.defaultGetDocumentString, shouldCacheResult = exports.defaultShouldCacheResult,
43
+ function useResponseCache({ cache = (0, in_memory_cache_js_1.createInMemoryCache)(), ttl: globalTtl = Infinity, session, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = exports.defaultBuildResponseCacheKey, getDocumentString = cache_document_str_js_1.defaultGetDocumentString, shouldCacheResult = exports.defaultShouldCacheResult,
45
44
  // eslint-disable-next-line dot-notation
46
- includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
47
- const appliedTransform = Symbol('responseCache.appliedTransform');
45
+ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, }) {
48
46
  const ignoredTypesMap = new Set(ignoredTypes);
49
47
  // never cache Introspections
50
48
  ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
@@ -54,38 +52,106 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
54
52
  addPlugin((0, cache_document_str_js_1.useCacheDocumentString)());
55
53
  }
56
54
  },
57
- onSchemaChange({ schema, replaceSchema }) {
55
+ async onExecute(onExecuteParams) {
58
56
  var _a;
59
- // @ts-expect-error See https://github.com/graphql/graphql-js/pull/3511 - remove this comments once merged
60
- if (((_a = schema.extensions) === null || _a === void 0 ? void 0 : _a[appliedTransform]) === true) {
61
- return;
57
+ let documentChanged = false;
58
+ const newDocument = (0, graphql_1.visit)(onExecuteParams.args.document, {
59
+ SelectionSet(node) {
60
+ if (!node.selections.some(selection => selection.kind === graphql_1.Kind.FIELD && selection.name.value === '__typename')) {
61
+ documentChanged = true;
62
+ return {
63
+ ...node,
64
+ selections: [
65
+ {
66
+ kind: graphql_1.Kind.FIELD,
67
+ name: {
68
+ kind: graphql_1.Kind.NAME,
69
+ value: '__typename',
70
+ },
71
+ },
72
+ ...node.selections,
73
+ ],
74
+ };
75
+ }
76
+ return node;
77
+ },
78
+ });
79
+ if (documentChanged) {
80
+ onExecuteParams.setExecuteFn(function typeNameAddedExecute() {
81
+ return onExecuteParams.executeFn({
82
+ ...onExecuteParams.args,
83
+ document: newDocument,
84
+ });
85
+ });
62
86
  }
63
- const patchedSchema = applyResponseCacheLogic(schema, idFields);
64
- patchedSchema.extensions = {
65
- ...patchedSchema.extensions,
66
- [appliedTransform]: true,
67
- };
68
- replaceSchema(patchedSchema);
69
- },
70
- async onExecute(ctx) {
71
- var _a;
72
87
  const identifier = new Map();
73
88
  const types = new Set();
74
- const context = {
75
- identifier,
76
- types,
77
- currentTtl: undefined,
78
- ttlPerType,
79
- ignoredTypesMap,
80
- skip: false,
89
+ let currentTtl;
90
+ let skip = false;
91
+ const processResult = (result) => {
92
+ var _a;
93
+ return (0, utils_1.visitResult)(result, {
94
+ document: onExecuteParams.args.document,
95
+ variables: onExecuteParams.args.variableValues,
96
+ operationName: (_a = onExecuteParams.args.operationName) !== null && _a !== void 0 ? _a : undefined,
97
+ rootValue: onExecuteParams.args.rootValue,
98
+ context: onExecuteParams.args.contextValue,
99
+ }, onExecuteParams.args.schema, new Proxy({}, {
100
+ get(_, typename) {
101
+ let typenameCalled = 0;
102
+ return new Proxy((val) => val, {
103
+ // Needed for leaf values such as scalars, enums etc
104
+ // They don't have fields so visitResult expects functions for those
105
+ apply(_, __, [val]) {
106
+ return val;
107
+ },
108
+ get(_, fieldName) {
109
+ if (documentChanged) {
110
+ if (fieldName === '__typename') {
111
+ typenameCalled++;
112
+ }
113
+ if (fieldName === '__leave') {
114
+ /**
115
+ * The visitResult function is called for each field in the selection set.
116
+ * But visitResult function looks for __typename field visitor even if it is not there in the document
117
+ * So it calls __typename field visitor twice if it is also in the selection set.
118
+ * That's why we need to count the number of times it is called.
119
+ *
120
+ * Default call of __typename https://github.com/ardatan/graphql-tools/blob/master/packages/utils/src/visitResult.ts#L277
121
+ * Call for the field node https://github.com/ardatan/graphql-tools/blob/master/packages/utils/src/visitResult.ts#L272
122
+ */
123
+ if (typenameCalled < 2) {
124
+ return (root) => {
125
+ delete root.__typename;
126
+ return root;
127
+ };
128
+ }
129
+ }
130
+ }
131
+ if (idFields.includes(fieldName)) {
132
+ if (ignoredTypesMap.has(typename)) {
133
+ skip = true;
134
+ }
135
+ if (skip === true) {
136
+ return;
137
+ }
138
+ return (id) => {
139
+ identifier.set(`${typename}:${id}`, { typename, id });
140
+ types.add(typename);
141
+ if (typename in ttlPerType) {
142
+ currentTtl = calculateTtl(ttlPerType[typename], currentTtl);
143
+ }
144
+ return id;
145
+ };
146
+ }
147
+ return undefined;
148
+ },
149
+ });
150
+ },
151
+ }));
81
152
  };
82
- ctx.extendContext({
83
- [contextSymbol]: context,
84
- });
85
- if (isMutation(ctx.args.document)) {
86
- if (invalidateViaMutation === false) {
87
- return;
88
- }
153
+ const operationAST = (0, graphql_1.getOperationAST)(onExecuteParams.args.document, onExecuteParams.args.operationName);
154
+ if (invalidateViaMutation !== false && (operationAST === null || operationAST === void 0 ? void 0 : operationAST.operation) === 'mutation') {
89
155
  return {
90
156
  onExecuteDone({ result, setResult }) {
91
157
  if ((0, core_1.isAsyncIterable)(result)) {
@@ -93,14 +159,15 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
93
159
  console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
94
160
  return;
95
161
  }
96
- cache.invalidate(context.identifier.values());
162
+ const processedResult = processResult(result);
163
+ cache.invalidate(identifier.values());
97
164
  if (includeExtensionMetadata) {
98
165
  setResult({
99
- ...result,
166
+ ...processedResult,
100
167
  extensions: {
101
- ...result.extensions,
168
+ ...processedResult.extensions,
102
169
  responseCache: {
103
- invalidatedEntities: Array.from(context.identifier.values()),
170
+ invalidatedEntities: Array.from(identifier.values()),
104
171
  },
105
172
  },
106
173
  });
@@ -109,16 +176,16 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
109
176
  };
110
177
  }
111
178
  const operationId = await buildResponseCacheKey({
112
- documentString: getDocumentString(ctx.args),
113
- variableValues: ctx.args.variableValues,
114
- operationName: ctx.args.operationName,
115
- sessionId: session(ctx.args.contextValue),
179
+ documentString: getDocumentString(onExecuteParams.args),
180
+ variableValues: onExecuteParams.args.variableValues,
181
+ operationName: onExecuteParams.args.operationName,
182
+ sessionId: session(onExecuteParams.args.contextValue),
116
183
  });
117
- if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
184
+ if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(onExecuteParams.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
118
185
  const cachedResponse = await cache.get(operationId);
119
186
  if (cachedResponse != null) {
120
187
  if (includeExtensionMetadata) {
121
- ctx.setResultAndStopExecution({
188
+ onExecuteParams.setResultAndStopExecution({
122
189
  ...cachedResponse,
123
190
  extensions: {
124
191
  responseCache: {
@@ -128,21 +195,21 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
128
195
  });
129
196
  }
130
197
  else {
131
- ctx.setResultAndStopExecution(cachedResponse);
198
+ onExecuteParams.setResultAndStopExecution(cachedResponse);
132
199
  }
133
200
  return;
134
201
  }
135
202
  }
136
203
  if (ttlPerSchemaCoordinate) {
137
- const typeInfo = new graphql_1.TypeInfo(ctx.args.schema);
138
- (0, graphql_1.visit)(ctx.args.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
204
+ const typeInfo = new graphql_1.TypeInfo(onExecuteParams.args.schema);
205
+ (0, graphql_1.visit)(onExecuteParams.args.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
139
206
  Field(fieldNode) {
140
207
  const parentType = typeInfo.getParentType();
141
208
  if (parentType) {
142
209
  const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
143
210
  const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
144
211
  if (maybeTtl !== undefined) {
145
- context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
212
+ currentTtl = calculateTtl(maybeTtl, currentTtl);
146
213
  }
147
214
  }
148
215
  },
@@ -150,24 +217,24 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
150
217
  }
151
218
  return {
152
219
  onExecuteDone({ result, setResult }) {
153
- var _a;
154
220
  if ((0, core_1.isAsyncIterable)(result)) {
155
221
  // eslint-disable-next-line no-console
156
222
  console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
157
223
  return;
158
224
  }
159
- if (context.skip) {
225
+ const processedResult = processResult(result);
226
+ if (skip) {
160
227
  return;
161
228
  }
162
- if (!shouldCacheResult({ result })) {
229
+ if (!shouldCacheResult({ result: processedResult })) {
163
230
  return;
164
231
  }
165
232
  // we only use the global ttl if no currentTtl has been determined.
166
- const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
233
+ const finalTtl = currentTtl !== null && currentTtl !== void 0 ? currentTtl : globalTtl;
167
234
  if (finalTtl === 0) {
168
235
  if (includeExtensionMetadata) {
169
236
  setResult({
170
- ...result,
237
+ ...processedResult,
171
238
  extensions: {
172
239
  responseCache: {
173
240
  hit: false,
@@ -178,10 +245,10 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
178
245
  }
179
246
  return;
180
247
  }
181
- cache.set(operationId, result, identifier.values(), finalTtl);
248
+ cache.set(operationId, processedResult, identifier.values(), finalTtl);
182
249
  if (includeExtensionMetadata) {
183
250
  setResult({
184
- ...result,
251
+ ...processedResult,
185
252
  extensions: {
186
253
  responseCache: {
187
254
  hit: false,
@@ -197,59 +264,9 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
197
264
  };
198
265
  }
199
266
  exports.useResponseCache = useResponseCache;
200
- function isOperationDefinition(node) {
201
- return (node === null || node === void 0 ? void 0 : node.kind) === 'OperationDefinition';
202
- }
203
267
  function calculateTtl(typeTtl, currentTtl) {
204
268
  if (typeof currentTtl === 'number') {
205
269
  return Math.min(currentTtl, typeTtl);
206
270
  }
207
271
  return typeTtl;
208
272
  }
209
- function isMutation(doc) {
210
- return doc.definitions.find(isOperationDefinition).operation === 'mutation';
211
- }
212
- function runWith(input, callback) {
213
- if (input instanceof Promise) {
214
- input.then(callback, () => undefined);
215
- }
216
- else {
217
- callback(input);
218
- }
219
- }
220
- function applyResponseCacheLogic(schema, idFieldNames) {
221
- return (0, utils_1.mapSchema)(schema, {
222
- [utils_1.MapperKind.OBJECT_FIELD](fieldConfig, fieldName, typename) {
223
- if (idFieldNames.includes(fieldName)) {
224
- return {
225
- ...fieldConfig,
226
- resolve(src, args, context, info) {
227
- var _a;
228
- const result = ((_a = fieldConfig.resolve) !== null && _a !== void 0 ? _a : graphql_1.defaultFieldResolver)(src, args, context, info);
229
- runWith(result, (id) => {
230
- const ctx = context[contextSymbol];
231
- if (ctx !== undefined) {
232
- if (ctx.ignoredTypesMap.has(typename)) {
233
- ctx.skip = true;
234
- }
235
- if (ctx.skip === true) {
236
- ctx.skip = true;
237
- return;
238
- }
239
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
240
- // @ts-ignore TODO: investigate what to do if id is something unexpected
241
- ctx.identifier.set(`${typename}:${id}`, { typename, id });
242
- ctx.types.add(typename);
243
- if (typename in ctx.ttlPerType) {
244
- ctx.currentTtl = calculateTtl(ctx.ttlPerType[typename], ctx.currentTtl);
245
- }
246
- }
247
- });
248
- return result;
249
- },
250
- };
251
- }
252
- return fieldConfig;
253
- },
254
- });
255
- }
package/esm/plugin.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import { isAsyncIterable } from '@envelop/core';
2
- import { MapperKind, mapSchema } from '@graphql-tools/utils';
3
- import { visit, TypeInfo, visitWithTypeInfo, defaultFieldResolver, } from 'graphql';
2
+ import { visitResult } from '@graphql-tools/utils';
3
+ import { visit, TypeInfo, visitWithTypeInfo, getOperationAST, Kind, } from 'graphql';
4
4
  import jsonStableStringify from 'fast-json-stable-stringify';
5
5
  import { createInMemoryCache } from './in-memory-cache.js';
6
6
  import { hashSHA256 } from './hashSHA256.js';
7
7
  import { defaultGetDocumentString, useCacheDocumentString } from './cache-document-str.js';
8
- const contextSymbol = Symbol('responseCache');
9
8
  /**
10
9
  * Default function used for building the response cache key.
11
10
  * It is exported here for advanced use-cases. E.g. if you want to short circuit and serve responses from the cache on a global level in order to completely by-pass the GraphQL flow.
@@ -35,10 +34,9 @@ export const defaultShouldCacheResult = (params) => {
35
34
  }
36
35
  return true;
37
36
  };
38
- export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session = () => null, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = defaultShouldCacheResult,
37
+ export function useResponseCache({ cache = createInMemoryCache(), ttl: globalTtl = Infinity, session, enabled, ignoredTypes = [], ttlPerType = {}, ttlPerSchemaCoordinate = {}, idFields = ['id'], invalidateViaMutation = true, buildResponseCacheKey = defaultBuildResponseCacheKey, getDocumentString = defaultGetDocumentString, shouldCacheResult = defaultShouldCacheResult,
39
38
  // eslint-disable-next-line dot-notation
40
- includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, } = {}) {
41
- const appliedTransform = Symbol('responseCache.appliedTransform');
39
+ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, }) {
42
40
  const ignoredTypesMap = new Set(ignoredTypes);
43
41
  // never cache Introspections
44
42
  ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
@@ -48,38 +46,106 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
48
46
  addPlugin(useCacheDocumentString());
49
47
  }
50
48
  },
51
- onSchemaChange({ schema, replaceSchema }) {
49
+ async onExecute(onExecuteParams) {
52
50
  var _a;
53
- // @ts-expect-error See https://github.com/graphql/graphql-js/pull/3511 - remove this comments once merged
54
- if (((_a = schema.extensions) === null || _a === void 0 ? void 0 : _a[appliedTransform]) === true) {
55
- return;
51
+ let documentChanged = false;
52
+ const newDocument = visit(onExecuteParams.args.document, {
53
+ SelectionSet(node) {
54
+ if (!node.selections.some(selection => selection.kind === Kind.FIELD && selection.name.value === '__typename')) {
55
+ documentChanged = true;
56
+ return {
57
+ ...node,
58
+ selections: [
59
+ {
60
+ kind: Kind.FIELD,
61
+ name: {
62
+ kind: Kind.NAME,
63
+ value: '__typename',
64
+ },
65
+ },
66
+ ...node.selections,
67
+ ],
68
+ };
69
+ }
70
+ return node;
71
+ },
72
+ });
73
+ if (documentChanged) {
74
+ onExecuteParams.setExecuteFn(function typeNameAddedExecute() {
75
+ return onExecuteParams.executeFn({
76
+ ...onExecuteParams.args,
77
+ document: newDocument,
78
+ });
79
+ });
56
80
  }
57
- const patchedSchema = applyResponseCacheLogic(schema, idFields);
58
- patchedSchema.extensions = {
59
- ...patchedSchema.extensions,
60
- [appliedTransform]: true,
61
- };
62
- replaceSchema(patchedSchema);
63
- },
64
- async onExecute(ctx) {
65
- var _a;
66
81
  const identifier = new Map();
67
82
  const types = new Set();
68
- const context = {
69
- identifier,
70
- types,
71
- currentTtl: undefined,
72
- ttlPerType,
73
- ignoredTypesMap,
74
- skip: false,
83
+ let currentTtl;
84
+ let skip = false;
85
+ const processResult = (result) => {
86
+ var _a;
87
+ return visitResult(result, {
88
+ document: onExecuteParams.args.document,
89
+ variables: onExecuteParams.args.variableValues,
90
+ operationName: (_a = onExecuteParams.args.operationName) !== null && _a !== void 0 ? _a : undefined,
91
+ rootValue: onExecuteParams.args.rootValue,
92
+ context: onExecuteParams.args.contextValue,
93
+ }, onExecuteParams.args.schema, new Proxy({}, {
94
+ get(_, typename) {
95
+ let typenameCalled = 0;
96
+ return new Proxy((val) => val, {
97
+ // Needed for leaf values such as scalars, enums etc
98
+ // They don't have fields so visitResult expects functions for those
99
+ apply(_, __, [val]) {
100
+ return val;
101
+ },
102
+ get(_, fieldName) {
103
+ if (documentChanged) {
104
+ if (fieldName === '__typename') {
105
+ typenameCalled++;
106
+ }
107
+ if (fieldName === '__leave') {
108
+ /**
109
+ * The visitResult function is called for each field in the selection set.
110
+ * But visitResult function looks for __typename field visitor even if it is not there in the document
111
+ * So it calls __typename field visitor twice if it is also in the selection set.
112
+ * That's why we need to count the number of times it is called.
113
+ *
114
+ * Default call of __typename https://github.com/ardatan/graphql-tools/blob/master/packages/utils/src/visitResult.ts#L277
115
+ * Call for the field node https://github.com/ardatan/graphql-tools/blob/master/packages/utils/src/visitResult.ts#L272
116
+ */
117
+ if (typenameCalled < 2) {
118
+ return (root) => {
119
+ delete root.__typename;
120
+ return root;
121
+ };
122
+ }
123
+ }
124
+ }
125
+ if (idFields.includes(fieldName)) {
126
+ if (ignoredTypesMap.has(typename)) {
127
+ skip = true;
128
+ }
129
+ if (skip === true) {
130
+ return;
131
+ }
132
+ return (id) => {
133
+ identifier.set(`${typename}:${id}`, { typename, id });
134
+ types.add(typename);
135
+ if (typename in ttlPerType) {
136
+ currentTtl = calculateTtl(ttlPerType[typename], currentTtl);
137
+ }
138
+ return id;
139
+ };
140
+ }
141
+ return undefined;
142
+ },
143
+ });
144
+ },
145
+ }));
75
146
  };
76
- ctx.extendContext({
77
- [contextSymbol]: context,
78
- });
79
- if (isMutation(ctx.args.document)) {
80
- if (invalidateViaMutation === false) {
81
- return;
82
- }
147
+ const operationAST = getOperationAST(onExecuteParams.args.document, onExecuteParams.args.operationName);
148
+ if (invalidateViaMutation !== false && (operationAST === null || operationAST === void 0 ? void 0 : operationAST.operation) === 'mutation') {
83
149
  return {
84
150
  onExecuteDone({ result, setResult }) {
85
151
  if (isAsyncIterable(result)) {
@@ -87,14 +153,15 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
87
153
  console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
88
154
  return;
89
155
  }
90
- cache.invalidate(context.identifier.values());
156
+ const processedResult = processResult(result);
157
+ cache.invalidate(identifier.values());
91
158
  if (includeExtensionMetadata) {
92
159
  setResult({
93
- ...result,
160
+ ...processedResult,
94
161
  extensions: {
95
- ...result.extensions,
162
+ ...processedResult.extensions,
96
163
  responseCache: {
97
- invalidatedEntities: Array.from(context.identifier.values()),
164
+ invalidatedEntities: Array.from(identifier.values()),
98
165
  },
99
166
  },
100
167
  });
@@ -103,16 +170,16 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
103
170
  };
104
171
  }
105
172
  const operationId = await buildResponseCacheKey({
106
- documentString: getDocumentString(ctx.args),
107
- variableValues: ctx.args.variableValues,
108
- operationName: ctx.args.operationName,
109
- sessionId: session(ctx.args.contextValue),
173
+ documentString: getDocumentString(onExecuteParams.args),
174
+ variableValues: onExecuteParams.args.variableValues,
175
+ operationName: onExecuteParams.args.operationName,
176
+ sessionId: session(onExecuteParams.args.contextValue),
110
177
  });
111
- if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(ctx.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
178
+ if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(onExecuteParams.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
112
179
  const cachedResponse = await cache.get(operationId);
113
180
  if (cachedResponse != null) {
114
181
  if (includeExtensionMetadata) {
115
- ctx.setResultAndStopExecution({
182
+ onExecuteParams.setResultAndStopExecution({
116
183
  ...cachedResponse,
117
184
  extensions: {
118
185
  responseCache: {
@@ -122,21 +189,21 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
122
189
  });
123
190
  }
124
191
  else {
125
- ctx.setResultAndStopExecution(cachedResponse);
192
+ onExecuteParams.setResultAndStopExecution(cachedResponse);
126
193
  }
127
194
  return;
128
195
  }
129
196
  }
130
197
  if (ttlPerSchemaCoordinate) {
131
- const typeInfo = new TypeInfo(ctx.args.schema);
132
- visit(ctx.args.document, visitWithTypeInfo(typeInfo, {
198
+ const typeInfo = new TypeInfo(onExecuteParams.args.schema);
199
+ visit(onExecuteParams.args.document, visitWithTypeInfo(typeInfo, {
133
200
  Field(fieldNode) {
134
201
  const parentType = typeInfo.getParentType();
135
202
  if (parentType) {
136
203
  const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
137
204
  const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
138
205
  if (maybeTtl !== undefined) {
139
- context.currentTtl = calculateTtl(maybeTtl, context.currentTtl);
206
+ currentTtl = calculateTtl(maybeTtl, currentTtl);
140
207
  }
141
208
  }
142
209
  },
@@ -144,24 +211,24 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
144
211
  }
145
212
  return {
146
213
  onExecuteDone({ result, setResult }) {
147
- var _a;
148
214
  if (isAsyncIterable(result)) {
149
215
  // eslint-disable-next-line no-console
150
216
  console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
151
217
  return;
152
218
  }
153
- if (context.skip) {
219
+ const processedResult = processResult(result);
220
+ if (skip) {
154
221
  return;
155
222
  }
156
- if (!shouldCacheResult({ result })) {
223
+ if (!shouldCacheResult({ result: processedResult })) {
157
224
  return;
158
225
  }
159
226
  // we only use the global ttl if no currentTtl has been determined.
160
- const finalTtl = (_a = context.currentTtl) !== null && _a !== void 0 ? _a : globalTtl;
227
+ const finalTtl = currentTtl !== null && currentTtl !== void 0 ? currentTtl : globalTtl;
161
228
  if (finalTtl === 0) {
162
229
  if (includeExtensionMetadata) {
163
230
  setResult({
164
- ...result,
231
+ ...processedResult,
165
232
  extensions: {
166
233
  responseCache: {
167
234
  hit: false,
@@ -172,10 +239,10 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
172
239
  }
173
240
  return;
174
241
  }
175
- cache.set(operationId, result, identifier.values(), finalTtl);
242
+ cache.set(operationId, processedResult, identifier.values(), finalTtl);
176
243
  if (includeExtensionMetadata) {
177
244
  setResult({
178
- ...result,
245
+ ...processedResult,
179
246
  extensions: {
180
247
  responseCache: {
181
248
  hit: false,
@@ -190,59 +257,9 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
190
257
  },
191
258
  };
192
259
  }
193
- function isOperationDefinition(node) {
194
- return (node === null || node === void 0 ? void 0 : node.kind) === 'OperationDefinition';
195
- }
196
260
  function calculateTtl(typeTtl, currentTtl) {
197
261
  if (typeof currentTtl === 'number') {
198
262
  return Math.min(currentTtl, typeTtl);
199
263
  }
200
264
  return typeTtl;
201
265
  }
202
- function isMutation(doc) {
203
- return doc.definitions.find(isOperationDefinition).operation === 'mutation';
204
- }
205
- function runWith(input, callback) {
206
- if (input instanceof Promise) {
207
- input.then(callback, () => undefined);
208
- }
209
- else {
210
- callback(input);
211
- }
212
- }
213
- function applyResponseCacheLogic(schema, idFieldNames) {
214
- return mapSchema(schema, {
215
- [MapperKind.OBJECT_FIELD](fieldConfig, fieldName, typename) {
216
- if (idFieldNames.includes(fieldName)) {
217
- return {
218
- ...fieldConfig,
219
- resolve(src, args, context, info) {
220
- var _a;
221
- const result = ((_a = fieldConfig.resolve) !== null && _a !== void 0 ? _a : defaultFieldResolver)(src, args, context, info);
222
- runWith(result, (id) => {
223
- const ctx = context[contextSymbol];
224
- if (ctx !== undefined) {
225
- if (ctx.ignoredTypesMap.has(typename)) {
226
- ctx.skip = true;
227
- }
228
- if (ctx.skip === true) {
229
- ctx.skip = true;
230
- return;
231
- }
232
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
233
- // @ts-ignore TODO: investigate what to do if id is something unexpected
234
- ctx.identifier.set(`${typename}:${id}`, { typename, id });
235
- ctx.types.add(typename);
236
- if (typename in ctx.ttlPerType) {
237
- ctx.currentTtl = calculateTtl(ctx.ttlPerType[typename], ctx.currentTtl);
238
- }
239
- }
240
- });
241
- return result;
242
- },
243
- };
244
- }
245
- return fieldConfig;
246
- },
247
- });
248
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envelop/response-cache",
3
- "version": "3.0.0-alpha-cadd145.0",
3
+ "version": "3.0.0-alpha-0c1aa26.0",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "@envelop/core": "^2.4.0",
@@ -12,7 +12,7 @@ export declare type BuildResponseCacheKeyFunction = (params: {
12
12
  /** The name of the GraphQL operation that should be executed from within the document. */
13
13
  operationName?: Maybe<string>;
14
14
  /** optional sessionId for make unique cache keys based on the session. */
15
- sessionId?: Maybe<string>;
15
+ sessionId: Maybe<string>;
16
16
  }) => Promise<string>;
17
17
  export declare type GetDocumentStringFunction = (executionArgs: ExecutionArgs) => string;
18
18
  export declare type ShouldCacheResultFunction = (params: {
@@ -45,8 +45,22 @@ export declare type UseResponseCacheParameter<C = any> = {
45
45
  * Return `null` or `undefined` to mark the session as public/global.
46
46
  * Creates a global session by default.
47
47
  * @param context GraphQL Context
48
+ *
49
+ * **Global Example:**
50
+ * ```ts
51
+ * useResponseCache({
52
+ * session: () => null,
53
+ * });
54
+ * ```
55
+ *
56
+ * **User Specific with global fallback example:**
57
+ * ```ts
58
+ * useResponseCache({
59
+ * session: (context) => context.user?.id ?? null,
60
+ * });
61
+ * ```
48
62
  */
49
- session?(context: C): string | undefined | null;
63
+ session(context: C): string | undefined | null;
50
64
  /**
51
65
  * Specify whether the cache should be used based on the context.
52
66
  * By default any request uses the cache.
@@ -105,4 +119,4 @@ export declare const defaultBuildResponseCacheKey: BuildResponseCacheKeyFunction
105
119
  * By default, results with errors (unexpected, EnvelopError, or GraphQLError) are not cached.
106
120
  */
107
121
  export declare const defaultShouldCacheResult: ShouldCacheResultFunction;
108
- export declare function useResponseCache({ cache, ttl: globalTtl, session, enabled, ignoredTypes, ttlPerType, ttlPerSchemaCoordinate, idFields, invalidateViaMutation, buildResponseCacheKey, getDocumentString, shouldCacheResult, includeExtensionMetadata, }?: UseResponseCacheParameter): Plugin;
122
+ export declare function useResponseCache({ cache, ttl: globalTtl, session, enabled, ignoredTypes, ttlPerType, ttlPerSchemaCoordinate, idFields, invalidateViaMutation, buildResponseCacheKey, getDocumentString, shouldCacheResult, includeExtensionMetadata, }: UseResponseCacheParameter): Plugin;