@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 +142 -122
- package/cjs/plugin.js +119 -102
- package/esm/plugin.js +121 -104
- package/package.json +1 -1
- package/typings/plugin.d.ts +17 -3
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({
|
|
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(
|
|
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({
|
|
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 }) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
55
|
+
async onExecute(onExecuteParams) {
|
|
58
56
|
var _a;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
162
|
+
const processedResult = processResult(result);
|
|
163
|
+
cache.invalidate(identifier.values());
|
|
97
164
|
if (includeExtensionMetadata) {
|
|
98
165
|
setResult({
|
|
99
|
-
...
|
|
166
|
+
...processedResult,
|
|
100
167
|
extensions: {
|
|
101
|
-
...
|
|
168
|
+
...processedResult.extensions,
|
|
102
169
|
responseCache: {
|
|
103
|
-
invalidatedEntities: Array.from(
|
|
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(
|
|
113
|
-
variableValues:
|
|
114
|
-
operationName:
|
|
115
|
-
sessionId: session(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
198
|
+
onExecuteParams.setResultAndStopExecution(cachedResponse);
|
|
132
199
|
}
|
|
133
200
|
return;
|
|
134
201
|
}
|
|
135
202
|
}
|
|
136
203
|
if (ttlPerSchemaCoordinate) {
|
|
137
|
-
const typeInfo = new graphql_1.TypeInfo(
|
|
138
|
-
(0, graphql_1.visit)(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
233
|
+
const finalTtl = currentTtl !== null && currentTtl !== void 0 ? currentTtl : globalTtl;
|
|
167
234
|
if (finalTtl === 0) {
|
|
168
235
|
if (includeExtensionMetadata) {
|
|
169
236
|
setResult({
|
|
170
|
-
...
|
|
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,
|
|
248
|
+
cache.set(operationId, processedResult, identifier.values(), finalTtl);
|
|
182
249
|
if (includeExtensionMetadata) {
|
|
183
250
|
setResult({
|
|
184
|
-
...
|
|
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 {
|
|
3
|
-
import { visit, TypeInfo, visitWithTypeInfo,
|
|
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
|
|
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
|
-
|
|
49
|
+
async onExecute(onExecuteParams) {
|
|
52
50
|
var _a;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
156
|
+
const processedResult = processResult(result);
|
|
157
|
+
cache.invalidate(identifier.values());
|
|
91
158
|
if (includeExtensionMetadata) {
|
|
92
159
|
setResult({
|
|
93
|
-
...
|
|
160
|
+
...processedResult,
|
|
94
161
|
extensions: {
|
|
95
|
-
...
|
|
162
|
+
...processedResult.extensions,
|
|
96
163
|
responseCache: {
|
|
97
|
-
invalidatedEntities: Array.from(
|
|
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(
|
|
107
|
-
variableValues:
|
|
108
|
-
operationName:
|
|
109
|
-
sessionId: session(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
onExecuteParams.setResultAndStopExecution(cachedResponse);
|
|
126
193
|
}
|
|
127
194
|
return;
|
|
128
195
|
}
|
|
129
196
|
}
|
|
130
197
|
if (ttlPerSchemaCoordinate) {
|
|
131
|
-
const typeInfo = new TypeInfo(
|
|
132
|
-
visit(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
227
|
+
const finalTtl = currentTtl !== null && currentTtl !== void 0 ? currentTtl : globalTtl;
|
|
161
228
|
if (finalTtl === 0) {
|
|
162
229
|
if (includeExtensionMetadata) {
|
|
163
230
|
setResult({
|
|
164
|
-
...
|
|
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,
|
|
242
|
+
cache.set(operationId, processedResult, identifier.values(), finalTtl);
|
|
176
243
|
if (includeExtensionMetadata) {
|
|
177
244
|
setResult({
|
|
178
|
-
...
|
|
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
package/typings/plugin.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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, }
|
|
122
|
+
export declare function useResponseCache({ cache, ttl: globalTtl, session, enabled, ignoredTypes, ttlPerType, ttlPerSchemaCoordinate, idFields, invalidateViaMutation, buildResponseCacheKey, getDocumentString, shouldCacheResult, includeExtensionMetadata, }: UseResponseCacheParameter): Plugin;
|