1
1
/* eslint-disable no-console */
2
- import { Plugin } from '@envelop/core' ;
3
- import { DocumentNode , Source , ExecutionArgs , ExecutionResult } from 'graphql' ;
4
- import { compileQuery , isCompiledQuery , CompilerOptions , CompiledQuery } from 'graphql-jit' ;
2
+ import { EnvelopError , Plugin , PromiseOrValue } from '@envelop/core' ;
3
+ import { DocumentNode , Source , ExecutionArgs , ExecutionResult , print , GraphQLSchema , getOperationAST } from 'graphql' ;
4
+ import { compileQuery , CompilerOptions , CompiledQuery } from 'graphql-jit' ;
5
5
import lru from 'tiny-lru' ;
6
6
7
7
const DEFAULT_MAX = 1000 ;
8
8
const DEFAULT_TTL = 3600000 ;
9
9
10
- type JITCacheEntry = {
11
- query : CompiledQuery [ 'query' ] ;
12
- subscribe ?: CompiledQuery [ 'subscribe' ] ;
13
- } ;
10
+ type CompilationResult = ReturnType < typeof compileQuery > ;
14
11
15
12
export interface JITCache {
16
- get ( key : string ) : JITCacheEntry | undefined ;
17
- set ( key : string , value : JITCacheEntry ) : void ;
13
+ get ( key : string ) : CompilationResult | undefined ;
14
+ set ( key : string , value : CompilationResult ) : void ;
15
+ }
16
+
17
+ function isSource ( source : any ) : source is Source {
18
+ return source . body != null ;
19
+ }
20
+
21
+ function isCompiledQuery ( compilationResult : any ) : compilationResult is CompiledQuery < unknown , unknown > {
22
+ return compilationResult . query != null ;
18
23
}
19
24
25
+ const MUST_DEFINE_OPERATION_NAME_ERR = 'Must provide operation name if query contains multiple operations.' ;
26
+
20
27
export const useGraphQlJit = (
21
28
compilerOptions : Partial < CompilerOptions > = { } ,
22
29
pluginOptions : {
@@ -36,74 +43,89 @@ export const useGraphQlJit = (
36
43
) : Plugin => {
37
44
const documentSourceMap = new WeakMap < DocumentNode , string > ( ) ;
38
45
const jitCache =
39
- typeof pluginOptions . cache !== 'undefined' ? pluginOptions . cache : lru < JITCacheEntry > ( DEFAULT_MAX , DEFAULT_TTL ) ;
40
-
41
- function compile ( args : ExecutionArgs ) {
42
- const compilationResult = compileQuery ( args . schema , args . document , args . operationName ?? undefined , compilerOptions ) ;
43
-
44
- if ( isCompiledQuery ( compilationResult ) ) {
45
- return compilationResult ;
46
- } else {
47
- if ( pluginOptions ?. onError ) {
48
- pluginOptions . onError ( compilationResult ) ;
49
- } else {
50
- console . error ( compilationResult ) ;
46
+ typeof pluginOptions . cache !== 'undefined' ? pluginOptions . cache : lru < CompilationResult > ( DEFAULT_MAX , DEFAULT_TTL ) ;
47
+
48
+ function getCacheKey ( document : DocumentNode , operationName ?: string ) {
49
+ if ( ! operationName ) {
50
+ const operationAST = getOperationAST ( document ) ;
51
+ if ( ! operationAST ) {
52
+ throw new EnvelopError ( MUST_DEFINE_OPERATION_NAME_ERR ) ;
51
53
}
52
- return {
53
- query : ( ) => compilationResult ,
54
- } ;
54
+ operationName = operationAST . name ?. value ;
55
55
}
56
+ const documentSource = getDocumentSource ( document ) ;
57
+ return `${ documentSource } ::${ operationName } ` ;
56
58
}
57
59
58
- function getCacheEntry ( args : ExecutionArgs ) : JITCacheEntry {
59
- const documentSource = documentSourceMap . get ( args . document ) ;
60
-
60
+ function getDocumentSource ( document : DocumentNode ) : string {
61
+ let documentSource = documentSourceMap . get ( document ) ;
61
62
if ( ! documentSource ) {
62
- return compile ( args ) ;
63
+ documentSource = print ( document ) ;
64
+ documentSourceMap . set ( document , documentSource ) ;
63
65
}
66
+ return documentSource ;
67
+ }
64
68
65
- let compiledQuery = jitCache . get ( documentSource ) ;
69
+ function getCompilationResult ( schema : GraphQLSchema , document : DocumentNode , operationName ?: string ) {
70
+ const cacheKey = getCacheKey ( document , operationName ) ;
71
+
72
+ let compiledQuery = jitCache . get ( cacheKey ) ;
66
73
67
74
if ( ! compiledQuery ) {
68
- compiledQuery = compile ( args ) ;
69
- jitCache . set ( documentSource , compiledQuery ) ;
75
+ compiledQuery = compileQuery ( schema , document , operationName , compilerOptions ) ;
76
+ jitCache . set ( cacheKey , compiledQuery ) ;
70
77
}
71
78
72
79
return compiledQuery ;
73
80
}
74
81
75
- function jitExecute ( args : ExecutionArgs ) {
76
- const cacheEntry = getCacheEntry ( args ) ;
77
-
78
- return cacheEntry . query ( args . rootValue , args . contextValue , args . variableValues ) ;
79
- }
80
-
81
- function jitSubscribe ( args : ExecutionArgs ) {
82
- const cacheEntry = getCacheEntry ( args ) ;
83
-
84
- return cacheEntry . subscribe
85
- ? ( cacheEntry . subscribe ( args . rootValue , args . contextValue , args . variableValues ) as any )
86
- : cacheEntry . query ( args . rootValue , args . contextValue , args . variableValues ) ;
87
- }
88
-
89
82
return {
90
83
onParse ( { params : { source } } ) {
91
- const key = source instanceof Source ? source . body : source ;
84
+ const key = isSource ( source ) ? source . body : source ;
92
85
93
86
return ( { result } ) => {
94
87
if ( ! result || result instanceof Error ) return ;
95
88
96
89
documentSourceMap . set ( result , key ) ;
97
90
} ;
98
91
} ,
99
- async onExecute ( { args, setExecuteFn } ) {
92
+ onValidate ( { params, setResult } ) {
93
+ try {
94
+ const compilationResult = getCompilationResult ( params . schema , params . documentAST ) ;
95
+ if ( ! isCompiledQuery ( compilationResult ) && compilationResult ?. errors != null ) {
96
+ setResult ( compilationResult . errors ) ;
97
+ }
98
+ } catch ( e : any ) {
99
+ // Validate doesn't work in case of multiple operations
100
+ if ( e . message !== MUST_DEFINE_OPERATION_NAME_ERR ) {
101
+ throw e ;
102
+ }
103
+ }
104
+ } ,
105
+ async onExecute ( { args, setExecuteFn, setResultAndStopExecution } ) {
100
106
if ( ! pluginOptions . enableIf || ( pluginOptions . enableIf && ( await pluginOptions . enableIf ( args ) ) ) ) {
101
- setExecuteFn ( jitExecute ) ;
107
+ const compilationResult = getCompilationResult ( args . schema , args . document , args . operationName ?? undefined ) ;
108
+ if ( isCompiledQuery ( compilationResult ) ) {
109
+ setExecuteFn ( function jitExecute ( args ) : PromiseOrValue < ExecutionResult < any , any > > {
110
+ return compilationResult . query ( args . rootValue , args . contextValue , args . variableValues ) ;
111
+ } ) ;
112
+ } else if ( compilationResult != null ) {
113
+ setResultAndStopExecution ( compilationResult as ExecutionResult ) ;
114
+ }
102
115
}
103
116
} ,
104
- async onSubscribe ( { args, setSubscribeFn } ) {
117
+ async onSubscribe ( { args, setSubscribeFn, setResultAndStopExecution } ) {
105
118
if ( ! pluginOptions . enableIf || ( pluginOptions . enableIf && ( await pluginOptions . enableIf ( args ) ) ) ) {
106
- setSubscribeFn ( jitSubscribe ) ;
119
+ const compilationResult = getCompilationResult ( args . schema , args . document , args . operationName ?? undefined ) ;
120
+ if ( isCompiledQuery ( compilationResult ) ) {
121
+ setSubscribeFn ( function jitSubscribe ( args : ExecutionArgs ) {
122
+ return compilationResult . subscribe
123
+ ? compilationResult . subscribe ( args . rootValue , args . contextValue , args . variableValues )
124
+ : compilationResult . query ( args . rootValue , args . contextValue , args . variableValues ) ;
125
+ } as any ) ;
126
+ } else if ( compilationResult != null ) {
127
+ setResultAndStopExecution ( compilationResult as ExecutionResult ) ;
128
+ }
107
129
}
108
130
} ,
109
131
} ;
0 commit comments