1
- import { getNamedType , GraphQLOutputType , GraphQLList , GraphQLSchema , FieldNode } from 'graphql' ;
1
+ import { GraphQLSchema , FieldNode } from 'graphql' ;
2
2
3
3
import DataLoader from 'dataloader' ;
4
4
5
- import { delegateToSchema , SubschemaConfig } from '@graphql-tools/delegate' ;
6
- import { memoize2 , relocatedError } from '@graphql-tools/utils' ;
5
+ import {
6
+ SubschemaConfig ,
7
+ Transformer ,
8
+ DelegationContext ,
9
+ validateRequest ,
10
+ getExecutor ,
11
+ getDelegatingOperation ,
12
+ createRequestFromInfo ,
13
+ getDelegationContext ,
14
+ } from '@graphql-tools/delegate' ;
15
+ import { ExecutionRequest , ExecutionResult , memoize2 } from '@graphql-tools/utils' ;
7
16
8
17
import { BatchDelegateOptions } from './types' ;
9
18
10
- function createBatchFn < K = any > ( options : BatchDelegateOptions ) {
19
+ function createBatchFn < K = any > (
20
+ options : BatchDelegateOptions
21
+ ) : (
22
+ keys : ReadonlyArray < K > ,
23
+ request : ExecutionRequest ,
24
+ delegationContext : DelegationContext < any >
25
+ ) => Promise < Array < ExecutionResult < Record < string , any > > > > {
11
26
const argsFromKeys = options . argsFromKeys ?? ( ( keys : ReadonlyArray < K > ) => ( { ids : keys } ) ) ;
12
- const fieldName = options . fieldName ?? options . info . fieldName ;
13
- const { valuesFromResults, lazyOptionsFn } = options ;
14
-
15
- return async function batchFn ( keys : ReadonlyArray < K > ) {
16
- const results = await delegateToSchema ( {
17
- returnType : new GraphQLList ( getNamedType ( options . info . returnType ) as GraphQLOutputType ) ,
18
- onLocatedError : originalError => {
19
- if ( originalError . path == null ) {
20
- return originalError ;
21
- }
22
-
23
- const [ pathFieldName , pathNumber ] = originalError . path ;
24
-
25
- if ( pathFieldName !== fieldName ) {
26
- return originalError ;
27
- }
28
- const pathNumberType = typeof pathNumber ;
29
- if ( pathNumberType !== 'number' ) {
30
- return originalError ;
31
- }
32
-
33
- return relocatedError ( originalError , originalError . path . slice ( 0 , 0 ) . concat ( originalError . path . slice ( 2 ) ) ) ;
34
- } ,
27
+
28
+ const { validateRequest : shouldValidateRequest } = options ;
29
+
30
+ return async function batchFn (
31
+ keys : ReadonlyArray < K > ,
32
+ request : ExecutionRequest ,
33
+ delegationContext : DelegationContext < any >
34
+ ) {
35
+ const { fieldName, context, info } = delegationContext ;
36
+
37
+ const transformer = new Transformer ( {
38
+ ...delegationContext ,
35
39
args : argsFromKeys ( keys ) ,
36
- ...( lazyOptionsFn == null ? options : lazyOptionsFn ( options ) ) ,
37
40
} ) ;
38
41
39
- if ( results instanceof Error ) {
40
- return keys . map ( ( ) => results ) ;
42
+ const processedRequest = transformer . transformRequest ( request ) ;
43
+
44
+ if ( shouldValidateRequest ) {
45
+ validateRequest ( delegationContext , processedRequest . document ) ;
41
46
}
42
47
43
- const values = valuesFromResults == null ? results : valuesFromResults ( results , keys ) ;
48
+ const executor = getExecutor ( delegationContext ) ;
44
49
45
- return Array . isArray ( values ) ? values : keys . map ( ( ) => values ) ;
50
+ const batchResult = ( await executor ( {
51
+ ...processedRequest ,
52
+ context,
53
+ info,
54
+ } ) ) as ExecutionResult ;
55
+
56
+ return splitResult ( transformer . transformResult ( batchResult ) , fieldName , keys . length ) ;
46
57
} ;
47
58
}
48
59
@@ -60,23 +71,75 @@ const getLoadersMap = memoize2(function getLoadersMap<K, V, C>(
60
71
return new Map < string , DataLoader < K , V , C > > ( ) ;
61
72
} ) ;
62
73
63
- export function getLoader < K = any , V = any , C = K > ( options : BatchDelegateOptions < any > ) : DataLoader < K , V , C > {
64
- const fieldName = options . fieldName ?? options . info . fieldName ;
65
- const loaders = getLoadersMap < K , V , C > ( options . info . fieldNodes , options . schema ) ;
74
+ export function getLoader < K = any , C = K > ( options : BatchDelegateOptions < any > ) : DataLoader < K , ExecutionResult , C > {
75
+ const {
76
+ info,
77
+ operationName,
78
+ operation = getDelegatingOperation ( info . parentType , info . schema ) ,
79
+ fieldName = info . fieldName ,
80
+ returnType = info . returnType ,
81
+ selectionSet,
82
+ fieldNodes,
83
+ } = options ;
84
+
85
+ if ( operation !== 'query' && operation !== 'mutation' ) {
86
+ throw new Error ( `Batch delegation not possible for operation '${ operation } '.` ) ;
87
+ }
66
88
67
- let loader = loaders . get ( fieldName ) ;
89
+ const request = createRequestFromInfo ( {
90
+ info,
91
+ operation,
92
+ fieldName,
93
+ selectionSet,
94
+ fieldNodes,
95
+ operationName,
96
+ } ) ;
97
+
98
+ const delegationContext = getDelegationContext ( {
99
+ request,
100
+ ...options ,
101
+ operation,
102
+ fieldName,
103
+ returnType,
104
+ } ) ;
68
105
69
106
// Prevents the keys to be passed with the same structure
70
- const dataLoaderOptions : DataLoader . Options < any , any , any > = {
107
+ const dataLoaderOptions : DataLoader . Options < K , ExecutionResult , C > = {
71
108
cacheKeyFn : defaultCacheKeyFn ,
72
109
...options . dataLoaderOptions ,
73
110
} ;
74
111
112
+ const loaders = getLoadersMap < K , ExecutionResult , C > ( info . fieldNodes , options . schema ) ;
113
+ let loader = loaders . get ( fieldName ) ;
114
+
75
115
if ( loader === undefined ) {
76
116
const batchFn = createBatchFn ( options ) ;
77
- loader = new DataLoader < K , V , C > ( batchFn , dataLoaderOptions ) ;
117
+ loader = new DataLoader < K , ExecutionResult , C > (
118
+ keys => batchFn ( keys , request , delegationContext ) ,
119
+ dataLoaderOptions
120
+ ) ;
78
121
loaders . set ( fieldName , loader ) ;
79
122
}
80
123
81
124
return loader ;
82
125
}
126
+
127
+ function splitResult ( result : ExecutionResult , fieldName : string , numItems : number ) : Array < ExecutionResult > {
128
+ const { data, errors } = result ;
129
+ const fieldData = data ?. [ fieldName ] ;
130
+
131
+ if ( fieldData === undefined ) {
132
+ if ( errors === undefined ) {
133
+ return Array ( numItems ) . fill ( { } ) ;
134
+ }
135
+
136
+ return Array ( numItems ) . fill ( { errors } ) ;
137
+ }
138
+
139
+ return fieldData . map ( ( value : any ) => ( {
140
+ data : {
141
+ [ fieldName ] : value ,
142
+ } ,
143
+ errors,
144
+ } ) ) ;
145
+ }
0 commit comments