1
1
import jsonStableStringify from 'fast-json-stable-stringify' ;
2
2
import {
3
+ ASTVisitor ,
3
4
DocumentNode ,
4
5
ExecutionArgs ,
5
6
getOperationAST ,
@@ -16,6 +17,7 @@ import {
16
17
Maybe ,
17
18
ObjMap ,
18
19
OnExecuteDoneHookResult ,
20
+ OnExecuteHookResult ,
19
21
Plugin ,
20
22
} from '@envelop/core' ;
21
23
import {
@@ -205,63 +207,77 @@ export type ResponseCacheExecutionResult = ExecutionResult<
205
207
{ responseCache ?: ResponseCacheExtensions }
206
208
> ;
207
209
208
- const originalDocumentMap = new WeakMap < DocumentNode , DocumentNode > ( ) ;
209
- const addEntityInfosToDocument = memoize4 ( function addTypeNameToDocument (
210
+ const getDocumentWithMetadataAndTTL = memoize4 ( function addTypeNameToDocument (
210
211
document : DocumentNode ,
211
- addTypeNameToDocumentOpts : { invalidateViaMutation : boolean } ,
212
+ {
213
+ invalidateViaMutation,
214
+ ttlPerSchemaCoordinate,
215
+ } : {
216
+ invalidateViaMutation : boolean ;
217
+ ttlPerSchemaCoordinate ?: Record < string , number | undefined > ;
218
+ } ,
212
219
schema : any ,
213
220
idFieldByTypeName : Map < string , string > ,
214
- ) : DocumentNode {
221
+ ) : [ DocumentNode , number | undefined ] {
215
222
const typeInfo = new TypeInfo ( schema ) ;
216
- const newDocument = visit (
217
- document ,
218
- visitWithTypeInfo ( typeInfo , {
219
- OperationDefinition : {
220
- enter ( node ) : void | false {
221
- if ( ! addTypeNameToDocumentOpts . invalidateViaMutation && node . operation === 'mutation' ) {
222
- return false ;
223
- }
224
- if ( node . operation === 'subscription' ) {
225
- return false ;
226
- }
227
- } ,
223
+ let ttl : number | undefined ;
224
+ const visitor : ASTVisitor = {
225
+ OperationDefinition : {
226
+ enter ( node ) : void | false {
227
+ if ( ! invalidateViaMutation && node . operation === 'mutation' ) {
228
+ return false ;
229
+ }
230
+ if ( node . operation === 'subscription' ) {
231
+ return false ;
232
+ }
228
233
} ,
229
- SelectionSet ( node , _key ) {
234
+ } ,
235
+ ...( ttlPerSchemaCoordinate != null && {
236
+ Field ( fieldNode ) {
230
237
const parentType = typeInfo . getParentType ( ) ;
231
- const idField = parentType && idFieldByTypeName . get ( parentType . name ) ;
232
- return {
233
- ...node ,
234
- selections : [
235
- {
236
- kind : Kind . FIELD ,
237
- name : {
238
- kind : Kind . NAME ,
239
- value : '__typename' ,
240
- } ,
241
- alias : {
242
- kind : Kind . NAME ,
243
- value : '__responseCacheTypeName' ,
244
- } ,
245
- } ,
246
- ...( idField
247
- ? [
248
- {
249
- kind : Kind . FIELD ,
250
- name : { kind : Kind . NAME , value : idField } ,
251
- alias : { kind : Kind . NAME , value : '__responseCacheId' } ,
252
- } ,
253
- ]
254
- : [ ] ) ,
255
-
256
- ...node . selections ,
257
- ] ,
258
- } ;
238
+ if ( parentType ) {
239
+ const schemaCoordinate = `${ parentType . name } .${ fieldNode . name . value } ` ;
240
+ const maybeTtl = ttlPerSchemaCoordinate [ schemaCoordinate ] ;
241
+ if ( maybeTtl !== undefined ) {
242
+ ttl = calculateTtl ( maybeTtl , ttl ) ;
243
+ }
244
+ }
259
245
} ,
260
246
} ) ,
261
- ) ;
247
+ SelectionSet ( node , _key ) {
248
+ const parentType = typeInfo . getParentType ( ) ;
249
+ const idField = parentType && idFieldByTypeName . get ( parentType . name ) ;
250
+ return {
251
+ ...node ,
252
+ selections : [
253
+ {
254
+ kind : Kind . FIELD ,
255
+ name : {
256
+ kind : Kind . NAME ,
257
+ value : '__typename' ,
258
+ } ,
259
+ alias : {
260
+ kind : Kind . NAME ,
261
+ value : '__responseCacheTypeName' ,
262
+ } ,
263
+ } ,
264
+ ...( idField
265
+ ? [
266
+ {
267
+ kind : Kind . FIELD ,
268
+ name : { kind : Kind . NAME , value : idField } ,
269
+ alias : { kind : Kind . NAME , value : '__responseCacheId' } ,
270
+ } ,
271
+ ]
272
+ : [ ] ) ,
273
+
274
+ ...node . selections ,
275
+ ] ,
276
+ } ;
277
+ } ,
278
+ } ;
262
279
263
- originalDocumentMap . set ( newDocument , document ) ;
264
- return newDocument ;
280
+ return [ visit ( document , visitWithTypeInfo ( typeInfo , visitor ) ) , ttl ] ;
265
281
} ) ;
266
282
267
283
export function useResponseCache < PluginContext extends Record < string , any > = { } > ( {
@@ -289,7 +305,10 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
289
305
290
306
// never cache Introspections
291
307
ttlPerSchemaCoordinate = { 'Query.__schema' : 0 , ...ttlPerSchemaCoordinate } ;
292
- const addTypeNameToDocumentOpts = { invalidateViaMutation } ;
308
+ const documentMetadataOptions = {
309
+ queries : { invalidateViaMutation, ttlPerSchemaCoordinate } ,
310
+ mutations : { invalidateViaMutation } , // remove ttlPerSchemaCoordinate for mutations to skip TTL calculation
311
+ } ;
293
312
const idFieldByTypeName = new Map < string , string > ( ) ;
294
313
let schema : any ;
295
314
@@ -303,22 +322,6 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
303
322
}
304
323
305
324
return {
306
- onParse ( ) {
307
- return ( { result, replaceParseResult, context } ) => {
308
- if ( enabled && ! enabled ( context ) ) {
309
- return ;
310
- }
311
- if ( ! originalDocumentMap . has ( result ) && result . kind === Kind . DOCUMENT ) {
312
- const newDocument = addEntityInfosToDocument (
313
- result ,
314
- addTypeNameToDocumentOpts ,
315
- schema ,
316
- idFieldByTypeName ,
317
- ) ;
318
- replaceParseResult ( newDocument ) ;
319
- }
320
- } ;
321
- } ,
322
325
onSchemaChange ( { schema : newSchema } ) {
323
326
if ( schema === newSchema ) {
324
327
return ;
@@ -373,11 +376,35 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
373
376
}
374
377
const identifier = new Map < string , CacheEntityRecord > ( ) ;
375
378
const types = new Set < string > ( ) ;
379
+ let currentTtl : number | undefined ;
380
+ let skip = false ;
376
381
377
382
const sessionId = session ( onExecuteParams . args . contextValue ) ;
378
383
379
- let currentTtl : number | undefined ;
380
- let skip = false ;
384
+ function setExecutor ( {
385
+ execute,
386
+ onExecuteDone,
387
+ } : {
388
+ execute : typeof onExecuteParams . executeFn ;
389
+ onExecuteDone ?: OnExecuteHookResult < PluginContext > [ 'onExecuteDone' ] ;
390
+ } ) : OnExecuteHookResult < PluginContext > {
391
+ let executed = false ;
392
+ onExecuteParams . setExecuteFn ( args => {
393
+ executed = true ;
394
+ return execute ( args ) ;
395
+ } ) ;
396
+ return {
397
+ onExecuteDone ( params ) {
398
+ if ( ! executed ) {
399
+ // eslint-disable-next-line no-console
400
+ console . warn (
401
+ '[useResponseCache] The cached execute function was not called, another plugin might have overwritten it. Please check your plugin order.' ,
402
+ ) ;
403
+ }
404
+ return onExecuteDone ?.( params ) ;
405
+ } ,
406
+ } ;
407
+ }
381
408
382
409
function processResult ( data : any ) {
383
410
if ( data == null || typeof data !== 'object' ) {
@@ -450,15 +477,24 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
450
477
) ;
451
478
452
479
if ( operationAST ?. operation === 'mutation' ) {
453
- return {
480
+ return setExecutor ( {
481
+ execute ( args ) {
482
+ const [ document ] = getDocumentWithMetadataAndTTL (
483
+ args . document ,
484
+ documentMetadataOptions . mutations ,
485
+ args . schema ,
486
+ idFieldByTypeName ,
487
+ ) ;
488
+ return onExecuteParams . executeFn ( { ...args , document } ) ;
489
+ } ,
454
490
onExecuteDone ( { result, setResult } ) {
455
491
if ( isAsyncIterable ( result ) ) {
456
492
return handleAsyncIterableResult ( invalidateCache ) ;
457
493
}
458
494
459
495
return invalidateCache ( result , setResult ) ;
460
496
} ,
461
- } ;
497
+ } ) ;
462
498
}
463
499
}
464
500
@@ -473,33 +509,12 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
473
509
const cachedResponse = ( await cache . get ( cacheKey ) ) as ResponseCacheExecutionResult ;
474
510
475
511
if ( cachedResponse != null ) {
476
- if ( includeExtensionMetadata ) {
477
- onExecuteParams . setResultAndStopExecution (
478
- resultWithMetadata ( cachedResponse , { hit : true } ) ,
479
- ) ;
480
- } else {
481
- onExecuteParams . setResultAndStopExecution ( cachedResponse ) ;
482
- }
483
- return ;
484
- }
485
-
486
- if ( ttlPerSchemaCoordinate ) {
487
- const typeInfo = new TypeInfo ( onExecuteParams . args . schema ) ;
488
- visit (
489
- onExecuteParams . args . document ,
490
- visitWithTypeInfo ( typeInfo , {
491
- Field ( fieldNode ) {
492
- const parentType = typeInfo . getParentType ( ) ;
493
- if ( parentType ) {
494
- const schemaCoordinate = `${ parentType . name } .${ fieldNode . name . value } ` ;
495
- const maybeTtl = ttlPerSchemaCoordinate [ schemaCoordinate ] ;
496
- if ( maybeTtl !== undefined ) {
497
- currentTtl = calculateTtl ( maybeTtl , currentTtl ) ;
498
- }
499
- }
500
- } ,
501
- } ) ,
502
- ) ;
512
+ return setExecutor ( {
513
+ execute : ( ) =>
514
+ includeExtensionMetadata
515
+ ? resultWithMetadata ( cachedResponse , { hit : true } )
516
+ : cachedResponse ,
517
+ } ) ;
503
518
}
504
519
505
520
function maybeCacheResult (
@@ -523,15 +538,25 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
523
538
}
524
539
}
525
540
526
- return {
541
+ return setExecutor ( {
542
+ execute ( args ) {
543
+ const [ document , ttl ] = getDocumentWithMetadataAndTTL (
544
+ args . document ,
545
+ documentMetadataOptions . queries ,
546
+ schema ,
547
+ idFieldByTypeName ,
548
+ ) ;
549
+ currentTtl = ttl ;
550
+ return onExecuteParams . executeFn ( { ...args , document } ) ;
551
+ } ,
527
552
onExecuteDone ( { result, setResult } ) {
528
553
if ( isAsyncIterable ( result ) ) {
529
554
return handleAsyncIterableResult ( maybeCacheResult ) ;
530
555
}
531
556
532
557
return maybeCacheResult ( result , setResult ) ;
533
558
} ,
534
- } ;
559
+ } ) ;
535
560
} ,
536
561
} ;
537
562
}
0 commit comments