@envelop/response-cache 3.0.0-alpha-0d610c4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -124
- package/cjs/{hashSHA256.js → hash-sha256.js} +2 -2
- package/cjs/plugin.js +117 -102
- package/esm/{hashSHA256.js → hash-sha256.js} +2 -2
- package/esm/plugin.js +118 -103
- package/package.json +1 -1
- /package/typings/{hashSHA256.d.ts → hash-sha256.d.ts} +0 -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"`).
|
|
@@ -6,10 +6,10 @@ const sha256Browser = (bytes) => {
|
|
|
6
6
|
const hash = webCrypto.subtle.digest({ name: 'SHA-256' }, bytes);
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
8
|
Promise.resolve(hash)
|
|
9
|
-
.then(
|
|
9
|
+
.then(result => {
|
|
10
10
|
resolve(new Uint8Array(result));
|
|
11
11
|
})
|
|
12
|
-
.catch(
|
|
12
|
+
.catch(error => {
|
|
13
13
|
reject(error);
|
|
14
14
|
});
|
|
15
15
|
});
|
package/cjs/plugin.js
CHANGED
|
@@ -7,16 +7,15 @@ const utils_1 = require("@graphql-tools/utils");
|
|
|
7
7
|
const graphql_1 = require("graphql");
|
|
8
8
|
const fast_json_stable_stringify_1 = tslib_1.__importDefault(require("fast-json-stable-stringify"));
|
|
9
9
|
const in_memory_cache_js_1 = require("./in-memory-cache.js");
|
|
10
|
-
const
|
|
10
|
+
const hash_sha256_js_1 = require("./hash-sha256.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.
|
|
16
15
|
*/
|
|
17
16
|
const defaultBuildResponseCacheKey = params => {
|
|
18
17
|
var _a, _b, _c;
|
|
19
|
-
return (0,
|
|
18
|
+
return (0, hash_sha256_js_1.hashSHA256)([
|
|
20
19
|
params.documentString,
|
|
21
20
|
(_a = params.operationName) !== null && _a !== void 0 ? _a : '',
|
|
22
21
|
(0, fast_json_stable_stringify_1.default)((_b = params.variableValues) !== null && _b !== void 0 ? _b : {}),
|
|
@@ -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,104 @@ 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
|
+
*/ typenameCalled < 2) {
|
|
123
|
+
return (root) => {
|
|
124
|
+
delete root.__typename;
|
|
125
|
+
return root;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (idFields.includes(fieldName)) {
|
|
130
|
+
if (ignoredTypesMap.has(typename)) {
|
|
131
|
+
skip = true;
|
|
132
|
+
}
|
|
133
|
+
if (skip === true) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
return (id) => {
|
|
137
|
+
identifier.set(`${typename}:${id}`, { typename, id });
|
|
138
|
+
types.add(typename);
|
|
139
|
+
if (typename in ttlPerType) {
|
|
140
|
+
currentTtl = calculateTtl(ttlPerType[typename], currentTtl);
|
|
141
|
+
}
|
|
142
|
+
return id;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
}));
|
|
81
150
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
-
if (isMutation(ctx.args.document)) {
|
|
86
|
-
if (invalidateViaMutation === false) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
151
|
+
const operationAST = (0, graphql_1.getOperationAST)(onExecuteParams.args.document, onExecuteParams.args.operationName);
|
|
152
|
+
if (invalidateViaMutation !== false && (operationAST === null || operationAST === void 0 ? void 0 : operationAST.operation) === 'mutation') {
|
|
89
153
|
return {
|
|
90
154
|
onExecuteDone({ result, setResult }) {
|
|
91
155
|
if ((0, core_1.isAsyncIterable)(result)) {
|
|
@@ -93,14 +157,15 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
93
157
|
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
94
158
|
return;
|
|
95
159
|
}
|
|
96
|
-
|
|
160
|
+
const processedResult = processResult(result);
|
|
161
|
+
cache.invalidate(identifier.values());
|
|
97
162
|
if (includeExtensionMetadata) {
|
|
98
163
|
setResult({
|
|
99
|
-
...
|
|
164
|
+
...processedResult,
|
|
100
165
|
extensions: {
|
|
101
|
-
...
|
|
166
|
+
...processedResult.extensions,
|
|
102
167
|
responseCache: {
|
|
103
|
-
invalidatedEntities: Array.from(
|
|
168
|
+
invalidatedEntities: Array.from(identifier.values()),
|
|
104
169
|
},
|
|
105
170
|
},
|
|
106
171
|
});
|
|
@@ -109,16 +174,16 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
109
174
|
};
|
|
110
175
|
}
|
|
111
176
|
const operationId = await buildResponseCacheKey({
|
|
112
|
-
documentString: getDocumentString(
|
|
113
|
-
variableValues:
|
|
114
|
-
operationName:
|
|
115
|
-
sessionId: session(
|
|
177
|
+
documentString: getDocumentString(onExecuteParams.args),
|
|
178
|
+
variableValues: onExecuteParams.args.variableValues,
|
|
179
|
+
operationName: onExecuteParams.args.operationName,
|
|
180
|
+
sessionId: session(onExecuteParams.args.contextValue),
|
|
116
181
|
});
|
|
117
|
-
if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(
|
|
182
|
+
if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(onExecuteParams.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
|
|
118
183
|
const cachedResponse = await cache.get(operationId);
|
|
119
184
|
if (cachedResponse != null) {
|
|
120
185
|
if (includeExtensionMetadata) {
|
|
121
|
-
|
|
186
|
+
onExecuteParams.setResultAndStopExecution({
|
|
122
187
|
...cachedResponse,
|
|
123
188
|
extensions: {
|
|
124
189
|
responseCache: {
|
|
@@ -128,21 +193,21 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
128
193
|
});
|
|
129
194
|
}
|
|
130
195
|
else {
|
|
131
|
-
|
|
196
|
+
onExecuteParams.setResultAndStopExecution(cachedResponse);
|
|
132
197
|
}
|
|
133
198
|
return;
|
|
134
199
|
}
|
|
135
200
|
}
|
|
136
201
|
if (ttlPerSchemaCoordinate) {
|
|
137
|
-
const typeInfo = new graphql_1.TypeInfo(
|
|
138
|
-
(0, graphql_1.visit)(
|
|
202
|
+
const typeInfo = new graphql_1.TypeInfo(onExecuteParams.args.schema);
|
|
203
|
+
(0, graphql_1.visit)(onExecuteParams.args.document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
|
|
139
204
|
Field(fieldNode) {
|
|
140
205
|
const parentType = typeInfo.getParentType();
|
|
141
206
|
if (parentType) {
|
|
142
207
|
const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
|
|
143
208
|
const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
|
|
144
209
|
if (maybeTtl !== undefined) {
|
|
145
|
-
|
|
210
|
+
currentTtl = calculateTtl(maybeTtl, currentTtl);
|
|
146
211
|
}
|
|
147
212
|
}
|
|
148
213
|
},
|
|
@@ -150,24 +215,24 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
150
215
|
}
|
|
151
216
|
return {
|
|
152
217
|
onExecuteDone({ result, setResult }) {
|
|
153
|
-
var _a;
|
|
154
218
|
if ((0, core_1.isAsyncIterable)(result)) {
|
|
155
219
|
// eslint-disable-next-line no-console
|
|
156
220
|
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
157
221
|
return;
|
|
158
222
|
}
|
|
159
|
-
|
|
223
|
+
const processedResult = processResult(result);
|
|
224
|
+
if (skip) {
|
|
160
225
|
return;
|
|
161
226
|
}
|
|
162
|
-
if (!shouldCacheResult({ result })) {
|
|
227
|
+
if (!shouldCacheResult({ result: processedResult })) {
|
|
163
228
|
return;
|
|
164
229
|
}
|
|
165
230
|
// we only use the global ttl if no currentTtl has been determined.
|
|
166
|
-
const finalTtl =
|
|
231
|
+
const finalTtl = currentTtl !== null && currentTtl !== void 0 ? currentTtl : globalTtl;
|
|
167
232
|
if (finalTtl === 0) {
|
|
168
233
|
if (includeExtensionMetadata) {
|
|
169
234
|
setResult({
|
|
170
|
-
...
|
|
235
|
+
...processedResult,
|
|
171
236
|
extensions: {
|
|
172
237
|
responseCache: {
|
|
173
238
|
hit: false,
|
|
@@ -178,10 +243,10 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
178
243
|
}
|
|
179
244
|
return;
|
|
180
245
|
}
|
|
181
|
-
cache.set(operationId,
|
|
246
|
+
cache.set(operationId, processedResult, identifier.values(), finalTtl);
|
|
182
247
|
if (includeExtensionMetadata) {
|
|
183
248
|
setResult({
|
|
184
|
-
...
|
|
249
|
+
...processedResult,
|
|
185
250
|
extensions: {
|
|
186
251
|
responseCache: {
|
|
187
252
|
hit: false,
|
|
@@ -197,59 +262,9 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
197
262
|
};
|
|
198
263
|
}
|
|
199
264
|
exports.useResponseCache = useResponseCache;
|
|
200
|
-
function isOperationDefinition(node) {
|
|
201
|
-
return (node === null || node === void 0 ? void 0 : node.kind) === 'OperationDefinition';
|
|
202
|
-
}
|
|
203
265
|
function calculateTtl(typeTtl, currentTtl) {
|
|
204
266
|
if (typeof currentTtl === 'number') {
|
|
205
267
|
return Math.min(currentTtl, typeTtl);
|
|
206
268
|
}
|
|
207
269
|
return typeTtl;
|
|
208
270
|
}
|
|
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
|
-
}
|
|
@@ -3,10 +3,10 @@ const sha256Browser = (bytes) => {
|
|
|
3
3
|
const hash = webCrypto.subtle.digest({ name: 'SHA-256' }, bytes);
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
5
|
Promise.resolve(hash)
|
|
6
|
-
.then(
|
|
6
|
+
.then(result => {
|
|
7
7
|
resolve(new Uint8Array(result));
|
|
8
8
|
})
|
|
9
|
-
.catch(
|
|
9
|
+
.catch(error => {
|
|
10
10
|
reject(error);
|
|
11
11
|
});
|
|
12
12
|
});
|
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
|
-
import { hashSHA256 } from './
|
|
6
|
+
import { hashSHA256 } from './hash-sha256.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,104 @@ 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
|
+
*/ typenameCalled < 2) {
|
|
117
|
+
return (root) => {
|
|
118
|
+
delete root.__typename;
|
|
119
|
+
return root;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (idFields.includes(fieldName)) {
|
|
124
|
+
if (ignoredTypesMap.has(typename)) {
|
|
125
|
+
skip = true;
|
|
126
|
+
}
|
|
127
|
+
if (skip === true) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
return (id) => {
|
|
131
|
+
identifier.set(`${typename}:${id}`, { typename, id });
|
|
132
|
+
types.add(typename);
|
|
133
|
+
if (typename in ttlPerType) {
|
|
134
|
+
currentTtl = calculateTtl(ttlPerType[typename], currentTtl);
|
|
135
|
+
}
|
|
136
|
+
return id;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
}));
|
|
75
144
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
if (isMutation(ctx.args.document)) {
|
|
80
|
-
if (invalidateViaMutation === false) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
145
|
+
const operationAST = getOperationAST(onExecuteParams.args.document, onExecuteParams.args.operationName);
|
|
146
|
+
if (invalidateViaMutation !== false && (operationAST === null || operationAST === void 0 ? void 0 : operationAST.operation) === 'mutation') {
|
|
83
147
|
return {
|
|
84
148
|
onExecuteDone({ result, setResult }) {
|
|
85
149
|
if (isAsyncIterable(result)) {
|
|
@@ -87,14 +151,15 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
87
151
|
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
88
152
|
return;
|
|
89
153
|
}
|
|
90
|
-
|
|
154
|
+
const processedResult = processResult(result);
|
|
155
|
+
cache.invalidate(identifier.values());
|
|
91
156
|
if (includeExtensionMetadata) {
|
|
92
157
|
setResult({
|
|
93
|
-
...
|
|
158
|
+
...processedResult,
|
|
94
159
|
extensions: {
|
|
95
|
-
...
|
|
160
|
+
...processedResult.extensions,
|
|
96
161
|
responseCache: {
|
|
97
|
-
invalidatedEntities: Array.from(
|
|
162
|
+
invalidatedEntities: Array.from(identifier.values()),
|
|
98
163
|
},
|
|
99
164
|
},
|
|
100
165
|
});
|
|
@@ -103,16 +168,16 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
103
168
|
};
|
|
104
169
|
}
|
|
105
170
|
const operationId = await buildResponseCacheKey({
|
|
106
|
-
documentString: getDocumentString(
|
|
107
|
-
variableValues:
|
|
108
|
-
operationName:
|
|
109
|
-
sessionId: session(
|
|
171
|
+
documentString: getDocumentString(onExecuteParams.args),
|
|
172
|
+
variableValues: onExecuteParams.args.variableValues,
|
|
173
|
+
operationName: onExecuteParams.args.operationName,
|
|
174
|
+
sessionId: session(onExecuteParams.args.contextValue),
|
|
110
175
|
});
|
|
111
|
-
if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(
|
|
176
|
+
if (((_a = enabled === null || enabled === void 0 ? void 0 : enabled(onExecuteParams.args.contextValue)) !== null && _a !== void 0 ? _a : true) === true) {
|
|
112
177
|
const cachedResponse = await cache.get(operationId);
|
|
113
178
|
if (cachedResponse != null) {
|
|
114
179
|
if (includeExtensionMetadata) {
|
|
115
|
-
|
|
180
|
+
onExecuteParams.setResultAndStopExecution({
|
|
116
181
|
...cachedResponse,
|
|
117
182
|
extensions: {
|
|
118
183
|
responseCache: {
|
|
@@ -122,21 +187,21 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
122
187
|
});
|
|
123
188
|
}
|
|
124
189
|
else {
|
|
125
|
-
|
|
190
|
+
onExecuteParams.setResultAndStopExecution(cachedResponse);
|
|
126
191
|
}
|
|
127
192
|
return;
|
|
128
193
|
}
|
|
129
194
|
}
|
|
130
195
|
if (ttlPerSchemaCoordinate) {
|
|
131
|
-
const typeInfo = new TypeInfo(
|
|
132
|
-
visit(
|
|
196
|
+
const typeInfo = new TypeInfo(onExecuteParams.args.schema);
|
|
197
|
+
visit(onExecuteParams.args.document, visitWithTypeInfo(typeInfo, {
|
|
133
198
|
Field(fieldNode) {
|
|
134
199
|
const parentType = typeInfo.getParentType();
|
|
135
200
|
if (parentType) {
|
|
136
201
|
const schemaCoordinate = `${parentType.name}.${fieldNode.name.value}`;
|
|
137
202
|
const maybeTtl = ttlPerSchemaCoordinate[schemaCoordinate];
|
|
138
203
|
if (maybeTtl !== undefined) {
|
|
139
|
-
|
|
204
|
+
currentTtl = calculateTtl(maybeTtl, currentTtl);
|
|
140
205
|
}
|
|
141
206
|
}
|
|
142
207
|
},
|
|
@@ -144,24 +209,24 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
144
209
|
}
|
|
145
210
|
return {
|
|
146
211
|
onExecuteDone({ result, setResult }) {
|
|
147
|
-
var _a;
|
|
148
212
|
if (isAsyncIterable(result)) {
|
|
149
213
|
// eslint-disable-next-line no-console
|
|
150
214
|
console.warn('[useResponseCache] AsyncIterable returned from execute is currently unsupported.');
|
|
151
215
|
return;
|
|
152
216
|
}
|
|
153
|
-
|
|
217
|
+
const processedResult = processResult(result);
|
|
218
|
+
if (skip) {
|
|
154
219
|
return;
|
|
155
220
|
}
|
|
156
|
-
if (!shouldCacheResult({ result })) {
|
|
221
|
+
if (!shouldCacheResult({ result: processedResult })) {
|
|
157
222
|
return;
|
|
158
223
|
}
|
|
159
224
|
// we only use the global ttl if no currentTtl has been determined.
|
|
160
|
-
const finalTtl =
|
|
225
|
+
const finalTtl = currentTtl !== null && currentTtl !== void 0 ? currentTtl : globalTtl;
|
|
161
226
|
if (finalTtl === 0) {
|
|
162
227
|
if (includeExtensionMetadata) {
|
|
163
228
|
setResult({
|
|
164
|
-
...
|
|
229
|
+
...processedResult,
|
|
165
230
|
extensions: {
|
|
166
231
|
responseCache: {
|
|
167
232
|
hit: false,
|
|
@@ -172,10 +237,10 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
172
237
|
}
|
|
173
238
|
return;
|
|
174
239
|
}
|
|
175
|
-
cache.set(operationId,
|
|
240
|
+
cache.set(operationId, processedResult, identifier.values(), finalTtl);
|
|
176
241
|
if (includeExtensionMetadata) {
|
|
177
242
|
setResult({
|
|
178
|
-
...
|
|
243
|
+
...processedResult,
|
|
179
244
|
extensions: {
|
|
180
245
|
responseCache: {
|
|
181
246
|
hit: false,
|
|
@@ -190,59 +255,9 @@ includeExtensionMetadata = typeof process !== 'undefined' ? process.env['NODE_EN
|
|
|
190
255
|
},
|
|
191
256
|
};
|
|
192
257
|
}
|
|
193
|
-
function isOperationDefinition(node) {
|
|
194
|
-
return (node === null || node === void 0 ? void 0 : node.kind) === 'OperationDefinition';
|
|
195
|
-
}
|
|
196
258
|
function calculateTtl(typeTtl, currentTtl) {
|
|
197
259
|
if (typeof currentTtl === 'number') {
|
|
198
260
|
return Math.min(currentTtl, typeTtl);
|
|
199
261
|
}
|
|
200
262
|
return typeTtl;
|
|
201
263
|
}
|
|
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
|
File without changes
|