@envelop/response-cache 3.0.0-alpha-9e9dfbb.0 → 3.0.0-alpha-e41b2ae.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 +126 -124
- package/cjs/plugin.js +117 -100
- package/esm/plugin.js +119 -102
- package/package.json +1 -1
- package/typings/plugin.d.ts +14 -0
package/README.md
CHANGED
|
@@ -38,35 +38,37 @@ 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
47
|
useResponseCache({
|
|
48
48
|
// use global cache for all operations
|
|
49
|
-
session: () => null
|
|
50
|
-
})
|
|
51
|
-
]
|
|
52
|
-
})
|
|
49
|
+
session: () => null
|
|
50
|
+
})
|
|
51
|
+
]
|
|
52
|
+
})
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
Or, you may create the in-memory LRU cache explicitly.
|
|
56
56
|
|
|
57
57
|
```ts
|
|
58
|
-
import { envelop } from '@envelop/core'
|
|
59
|
-
import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
|
|
58
|
+
import { envelop } from '@envelop/core'
|
|
59
|
+
import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
|
|
60
60
|
|
|
61
|
-
const cache = createInMemoryCache()
|
|
61
|
+
const cache = createInMemoryCache()
|
|
62
62
|
|
|
63
63
|
const getEnveloped = envelop({
|
|
64
64
|
plugins: [
|
|
65
65
|
// ... other plugins ...
|
|
66
|
-
useResponseCache({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
66
|
+
useResponseCache({
|
|
67
|
+
cache,
|
|
68
|
+
session: () => null // use global cache for all operations
|
|
69
|
+
})
|
|
70
|
+
]
|
|
71
|
+
})
|
|
70
72
|
```
|
|
71
73
|
|
|
72
74
|
> Note: The in-memory LRU cache is not suitable for serverless deployments. Instead, consider the Redis cache provided by `@envelop/response-cache-redis`.
|
|
@@ -74,8 +76,8 @@ const getEnveloped = envelop({
|
|
|
74
76
|
### Cache based on session/user
|
|
75
77
|
|
|
76
78
|
```ts
|
|
77
|
-
import { envelop } from '@envelop/core'
|
|
78
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
79
|
+
import { envelop } from '@envelop/core'
|
|
80
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
79
81
|
|
|
80
82
|
const getEnveloped = envelop({
|
|
81
83
|
plugins: [
|
|
@@ -83,10 +85,10 @@ const getEnveloped = envelop({
|
|
|
83
85
|
useResponseCache({
|
|
84
86
|
ttl: 2000,
|
|
85
87
|
// context is the GraphQL context used for execution
|
|
86
|
-
session: context => String(context.user?.id)
|
|
87
|
-
})
|
|
88
|
-
]
|
|
89
|
-
})
|
|
88
|
+
session: context => String(context.user?.id)
|
|
89
|
+
})
|
|
90
|
+
]
|
|
91
|
+
})
|
|
90
92
|
```
|
|
91
93
|
|
|
92
94
|
### Redis Cache
|
|
@@ -103,31 +105,31 @@ In order to use the Redis cache, you need to:
|
|
|
103
105
|
- Create an instance of the Redis Cache and set to the `useResponseCache` plugin options
|
|
104
106
|
|
|
105
107
|
```ts
|
|
106
|
-
import { envelop } from '@envelop/core'
|
|
107
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
108
|
-
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'
|
|
109
111
|
|
|
110
|
-
import Redis from 'ioredis'
|
|
112
|
+
import Redis from 'ioredis'
|
|
111
113
|
|
|
112
114
|
const redis = new Redis({
|
|
113
115
|
host: 'my-redis-db.example.com',
|
|
114
116
|
port: '30652',
|
|
115
|
-
password: '1234567890'
|
|
116
|
-
})
|
|
117
|
+
password: '1234567890'
|
|
118
|
+
})
|
|
117
119
|
|
|
118
|
-
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')
|
|
119
121
|
|
|
120
|
-
const cache = createRedisCache({ redis })
|
|
122
|
+
const cache = createRedisCache({ redis })
|
|
121
123
|
|
|
122
124
|
const getEnveloped = envelop({
|
|
123
125
|
plugins: [
|
|
124
126
|
// ... other plugins ...
|
|
125
127
|
useResponseCache({
|
|
126
128
|
cache,
|
|
127
|
-
session: () => null
|
|
128
|
-
})
|
|
129
|
-
]
|
|
130
|
-
})
|
|
129
|
+
session: () => null // use global cache for all operations
|
|
130
|
+
})
|
|
131
|
+
]
|
|
132
|
+
})
|
|
131
133
|
```
|
|
132
134
|
|
|
133
135
|
> Note: In the Recipes below, be sure to provide your Redis `cache` instance with `useResponseCache({ cache })`.
|
|
@@ -137,18 +139,18 @@ const getEnveloped = envelop({
|
|
|
137
139
|
### Cache with maximum TTL
|
|
138
140
|
|
|
139
141
|
```ts
|
|
140
|
-
import { envelop } from '@envelop/core'
|
|
141
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
142
|
+
import { envelop } from '@envelop/core'
|
|
143
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
142
144
|
|
|
143
145
|
const getEnveloped = envelop({
|
|
144
146
|
plugins: [
|
|
145
147
|
// ... other plugins ...
|
|
146
148
|
useResponseCache({
|
|
147
149
|
ttl: 2000, // cached execution results become stale after 2 seconds
|
|
148
|
-
session: () => null
|
|
149
|
-
})
|
|
150
|
-
]
|
|
151
|
-
})
|
|
150
|
+
session: () => null // use global cache for all operations
|
|
151
|
+
})
|
|
152
|
+
]
|
|
153
|
+
})
|
|
152
154
|
```
|
|
153
155
|
|
|
154
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`.
|
|
@@ -156,8 +158,8 @@ const getEnveloped = envelop({
|
|
|
156
158
|
### Cache with custom TTL per object type
|
|
157
159
|
|
|
158
160
|
```ts
|
|
159
|
-
import { envelop } from '@envelop/core'
|
|
160
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
161
|
+
import { envelop } from '@envelop/core'
|
|
162
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
161
163
|
|
|
162
164
|
const getEnveloped = envelop({
|
|
163
165
|
plugins: [
|
|
@@ -166,18 +168,18 @@ const getEnveloped = envelop({
|
|
|
166
168
|
ttl: 2000,
|
|
167
169
|
ttlPerType: {
|
|
168
170
|
// cached execution results that contain a `Stock` object become stale after 500ms
|
|
169
|
-
Stock: 500
|
|
170
|
-
}
|
|
171
|
-
})
|
|
172
|
-
]
|
|
173
|
-
})
|
|
171
|
+
Stock: 500
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
})
|
|
174
176
|
```
|
|
175
177
|
|
|
176
178
|
### Cache with custom TTL per schema coordinate
|
|
177
179
|
|
|
178
180
|
```ts
|
|
179
|
-
import { envelop } from '@envelop/core'
|
|
180
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
181
|
+
import { envelop } from '@envelop/core'
|
|
182
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
181
183
|
|
|
182
184
|
const getEnveloped = envelop({
|
|
183
185
|
plugins: [
|
|
@@ -186,18 +188,18 @@ const getEnveloped = envelop({
|
|
|
186
188
|
ttl: 2000,
|
|
187
189
|
ttlPerSchemaCoordinate: {
|
|
188
190
|
// cached execution results that select the `Query.user` field become stale after 100ms
|
|
189
|
-
'Query.rocketCoordinates': 100
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
]
|
|
193
|
-
})
|
|
191
|
+
'Query.rocketCoordinates': 100
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
})
|
|
194
196
|
```
|
|
195
197
|
|
|
196
198
|
### Disable cache based on session/user
|
|
197
199
|
|
|
198
200
|
```ts
|
|
199
|
-
import { envelop } from '@envelop/core'
|
|
200
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
201
|
+
import { envelop } from '@envelop/core'
|
|
202
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
201
203
|
|
|
202
204
|
const getEnveloped = envelop({
|
|
203
205
|
plugins: [
|
|
@@ -206,10 +208,10 @@ const getEnveloped = envelop({
|
|
|
206
208
|
ttl: 2000,
|
|
207
209
|
// context is the GraphQL context used for execution
|
|
208
210
|
enabled: context => context.user?.role !== 'admin',
|
|
209
|
-
session: () => null
|
|
210
|
-
})
|
|
211
|
-
]
|
|
212
|
-
})
|
|
211
|
+
session: () => null
|
|
212
|
+
})
|
|
213
|
+
]
|
|
214
|
+
})
|
|
213
215
|
```
|
|
214
216
|
|
|
215
217
|
### Customize if result should be cached
|
|
@@ -217,7 +219,7 @@ const getEnveloped = envelop({
|
|
|
217
219
|
You can define a custom function used to check if a query operation execution result should be cached.
|
|
218
220
|
|
|
219
221
|
```ts
|
|
220
|
-
type ShouldCacheResultFunction = (params: { result: ExecutionResult }) =>
|
|
222
|
+
type ShouldCacheResultFunction = (params: { result: ExecutionResult }) => boolean
|
|
221
223
|
```
|
|
222
224
|
|
|
223
225
|
This is useful for advanced use-cases. E.g. if you want to
|
|
@@ -226,24 +228,24 @@ cache results with certain error types.
|
|
|
226
228
|
By default, the `defaultShouldCacheResult` function is used which never caches any query operation execution results that includes any errors (unexpected, EnvelopError, or GraphQLError).
|
|
227
229
|
|
|
228
230
|
```ts
|
|
229
|
-
import { envelop } from '@envelop/core'
|
|
230
|
-
import { useResponseCache, ShouldCacheResultFunction } from '@envelop/response-cache'
|
|
231
|
+
import { envelop } from '@envelop/core'
|
|
232
|
+
import { useResponseCache, ShouldCacheResultFunction } from '@envelop/response-cache'
|
|
231
233
|
|
|
232
234
|
export const defaultShouldCacheResult: ShouldCacheResultFunction = (params): Boolean => {
|
|
233
235
|
// cache any query operation execution result
|
|
234
236
|
// even if it includes errors
|
|
235
|
-
return true
|
|
236
|
-
}
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
237
239
|
|
|
238
240
|
const getEnveloped = envelop({
|
|
239
241
|
plugins: [
|
|
240
242
|
// ... other plugins ...
|
|
241
243
|
useResponseCache({
|
|
242
|
-
shouldCacheResult
|
|
243
|
-
session: () => null
|
|
244
|
-
})
|
|
245
|
-
]
|
|
246
|
-
})
|
|
244
|
+
shouldCacheResult: myCustomShouldCacheResult,
|
|
245
|
+
session: () => null
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
})
|
|
247
249
|
```
|
|
248
250
|
|
|
249
251
|
### Cache Introspection query operations
|
|
@@ -253,63 +255,63 @@ By default introspection query operations are not cached. In case you want to ca
|
|
|
253
255
|
**Infinite caching**
|
|
254
256
|
|
|
255
257
|
```ts
|
|
256
|
-
import { envelop } from '@envelop/core'
|
|
257
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
258
|
+
import { envelop } from '@envelop/core'
|
|
259
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
258
260
|
|
|
259
261
|
const getEnveloped = envelop({
|
|
260
262
|
plugins: [
|
|
261
263
|
// ... other plugins ...
|
|
262
264
|
useResponseCache({
|
|
263
265
|
ttlPerSchemaCoordinate: {
|
|
264
|
-
'Query.__schema': undefined
|
|
266
|
+
'Query.__schema': undefined // cache infinitely
|
|
265
267
|
},
|
|
266
|
-
session: () => null
|
|
267
|
-
})
|
|
268
|
-
]
|
|
269
|
-
})
|
|
268
|
+
session: () => null
|
|
269
|
+
})
|
|
270
|
+
]
|
|
271
|
+
})
|
|
270
272
|
```
|
|
271
273
|
|
|
272
274
|
**TTL caching**
|
|
273
275
|
|
|
274
276
|
```ts
|
|
275
|
-
import { envelop } from '@envelop/core'
|
|
276
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
277
|
+
import { envelop } from '@envelop/core'
|
|
278
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
277
279
|
|
|
278
280
|
const getEnveloped = envelop({
|
|
279
281
|
plugins: [
|
|
280
282
|
// ... other plugins ...
|
|
281
283
|
useResponseCache({
|
|
282
284
|
ttlPerSchemaCoordinate: {
|
|
283
|
-
'Query.__schema': 10_000
|
|
285
|
+
'Query.__schema': 10_000 // cache for 10 seconds
|
|
284
286
|
},
|
|
285
|
-
session: () => null
|
|
286
|
-
})
|
|
287
|
-
]
|
|
288
|
-
})
|
|
287
|
+
session: () => null
|
|
288
|
+
})
|
|
289
|
+
]
|
|
290
|
+
})
|
|
289
291
|
```
|
|
290
292
|
|
|
291
293
|
### Cache with maximum TTL
|
|
292
294
|
|
|
293
295
|
```ts
|
|
294
|
-
import { envelop } from '@envelop/core'
|
|
295
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
296
|
+
import { envelop } from '@envelop/core'
|
|
297
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
296
298
|
|
|
297
299
|
const getEnveloped = envelop({
|
|
298
300
|
plugins: [
|
|
299
301
|
// ... other plugins ...
|
|
300
302
|
useResponseCache({
|
|
301
303
|
ttl: 2000, // cached execution results become stale after 2 seconds
|
|
302
|
-
session: () => null
|
|
303
|
-
})
|
|
304
|
-
]
|
|
305
|
-
})
|
|
304
|
+
session: () => null
|
|
305
|
+
})
|
|
306
|
+
]
|
|
307
|
+
})
|
|
306
308
|
```
|
|
307
309
|
|
|
308
310
|
### Customize the fields that are used for building the cache ID
|
|
309
311
|
|
|
310
312
|
```ts
|
|
311
|
-
import { envelop } from '@envelop/core'
|
|
312
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
313
|
+
import { envelop } from '@envelop/core'
|
|
314
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
313
315
|
|
|
314
316
|
const getEnveloped = envelop({
|
|
315
317
|
plugins: [
|
|
@@ -318,17 +320,17 @@ const getEnveloped = envelop({
|
|
|
318
320
|
ttl: 2000,
|
|
319
321
|
// use the `_id` instead of `id` field.
|
|
320
322
|
idFields: ['_id'],
|
|
321
|
-
session: () => null
|
|
322
|
-
})
|
|
323
|
-
]
|
|
324
|
-
})
|
|
323
|
+
session: () => null
|
|
324
|
+
})
|
|
325
|
+
]
|
|
326
|
+
})
|
|
325
327
|
```
|
|
326
328
|
|
|
327
329
|
### Disable automatic cache invalidation via mutations
|
|
328
330
|
|
|
329
331
|
```ts
|
|
330
|
-
import { envelop } from '@envelop/core'
|
|
331
|
-
import { useResponseCache } from '@envelop/response-cache'
|
|
332
|
+
import { envelop } from '@envelop/core'
|
|
333
|
+
import { useResponseCache } from '@envelop/response-cache'
|
|
332
334
|
|
|
333
335
|
const getEnveloped = envelop({
|
|
334
336
|
plugins: [
|
|
@@ -337,21 +339,21 @@ const getEnveloped = envelop({
|
|
|
337
339
|
ttl: 2000,
|
|
338
340
|
// some might prefer invalidating based on a database write log
|
|
339
341
|
invalidateViaMutation: false,
|
|
340
|
-
session: () => null
|
|
341
|
-
})
|
|
342
|
-
]
|
|
343
|
-
})
|
|
342
|
+
session: () => null
|
|
343
|
+
})
|
|
344
|
+
]
|
|
345
|
+
})
|
|
344
346
|
```
|
|
345
347
|
|
|
346
348
|
### Invalidate Cache based on custom logic
|
|
347
349
|
|
|
348
350
|
```ts
|
|
349
|
-
import { envelop } from '@envelop/core'
|
|
350
|
-
import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
|
|
351
|
-
import { emitter } from './eventEmitter'
|
|
351
|
+
import { envelop } from '@envelop/core'
|
|
352
|
+
import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
|
|
353
|
+
import { emitter } from './eventEmitter'
|
|
352
354
|
|
|
353
355
|
// we create our cache instance, which allows calling all methods on it
|
|
354
|
-
const cache = createInMemoryCache()
|
|
356
|
+
const cache = createInMemoryCache()
|
|
355
357
|
|
|
356
358
|
const getEnveloped = envelop({
|
|
357
359
|
plugins: [
|
|
@@ -360,33 +362,33 @@ const getEnveloped = envelop({
|
|
|
360
362
|
ttl: 2000,
|
|
361
363
|
// we pass the cache instance to the request.
|
|
362
364
|
cache,
|
|
363
|
-
session: () => null
|
|
364
|
-
})
|
|
365
|
-
]
|
|
366
|
-
})
|
|
365
|
+
session: () => null
|
|
366
|
+
})
|
|
367
|
+
]
|
|
368
|
+
})
|
|
367
369
|
|
|
368
370
|
emitter.on('invalidate', resource => {
|
|
369
371
|
cache.invalidate([
|
|
370
372
|
{
|
|
371
373
|
typename: resource.type,
|
|
372
|
-
id: resource.id
|
|
373
|
-
}
|
|
374
|
-
])
|
|
375
|
-
})
|
|
374
|
+
id: resource.id
|
|
375
|
+
}
|
|
376
|
+
])
|
|
377
|
+
})
|
|
376
378
|
```
|
|
377
379
|
|
|
378
380
|
### Customize how cache ids are built
|
|
379
381
|
|
|
380
382
|
```ts
|
|
381
|
-
import { envelop } from '@envelop/core'
|
|
382
|
-
import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
|
|
383
|
-
import { emitter } from './eventEmitter'
|
|
383
|
+
import { envelop } from '@envelop/core'
|
|
384
|
+
import { useResponseCache, createInMemoryCache } from '@envelop/response-cache'
|
|
385
|
+
import { emitter } from './eventEmitter'
|
|
384
386
|
|
|
385
387
|
// we create our cache instance, which allows calling all methods on it
|
|
386
388
|
const cache = createInMemoryCache({
|
|
387
389
|
// in relay we have global unique ids, no need to use `typename:id`
|
|
388
|
-
makeId: (typename, id) => id ?? typename
|
|
389
|
-
})
|
|
390
|
+
makeId: (typename, id) => id ?? typename
|
|
391
|
+
})
|
|
390
392
|
|
|
391
393
|
const getEnveloped = envelop({
|
|
392
394
|
plugins: [
|
|
@@ -395,10 +397,10 @@ const getEnveloped = envelop({
|
|
|
395
397
|
ttl: 2000,
|
|
396
398
|
// we pass the cache instance to the request.
|
|
397
399
|
cache,
|
|
398
|
-
session: () => null
|
|
399
|
-
})
|
|
400
|
-
]
|
|
401
|
-
})
|
|
400
|
+
session: () => null
|
|
401
|
+
})
|
|
402
|
+
]
|
|
403
|
+
})
|
|
402
404
|
```
|
|
403
405
|
|
|
404
406
|
### Expose cache metadata via extensions
|
|
@@ -412,10 +414,10 @@ const getEnveloped = envelop({
|
|
|
412
414
|
useResponseCache({
|
|
413
415
|
ttl: 2000,
|
|
414
416
|
includeExtensionMetadata: true,
|
|
415
|
-
session: () => null
|
|
416
|
-
})
|
|
417
|
-
]
|
|
418
|
-
})
|
|
417
|
+
session: () => null
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
})
|
|
419
421
|
```
|
|
420
422
|
|
|
421
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.
|
|
@@ -44,7 +43,6 @@ exports.defaultShouldCacheResult = defaultShouldCacheResult;
|
|
|
44
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
45
|
includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, }) {
|
|
47
|
-
const appliedTransform = Symbol('responseCache.appliedTransform');
|
|
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.
|
|
@@ -38,7 +37,6 @@ export const defaultShouldCacheResult = (params) => {
|
|
|
38
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
39
|
includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_ENV'] === 'development' : false, }) {
|
|
41
|
-
const appliedTransform = Symbol('responseCache.appliedTransform');
|
|
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
|
@@ -45,6 +45,20 @@ 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
63
|
session(context: C): string | undefined | null;
|
|
50
64
|
/**
|