@@ -171,6 +171,142 @@ function generateValuesFromResults(resultExpression: string): (result: any) => a
171171 } ;
172172}
173173
174+ export interface PubSubOperationOptions {
175+ pubsubTopic : string ;
176+ pubsub ?: MeshPubSub | HivePubSub ;
177+ filterBy ?: string ;
178+ result ?: string ;
179+ }
180+
181+ export function getResolverForPubSubOperation ( opts : PubSubOperationOptions ) {
182+ const pubsubTopic = opts . pubsubTopic ;
183+ let subscribeFn = function subscriber (
184+ root : any ,
185+ args : Record < string , any > ,
186+ context : MeshContext ,
187+ info : GraphQLResolveInfo ,
188+ ) : MaybePromise < AsyncIterator < any > > {
189+ const resolverData = { root, args, context, info, env : process . env } ;
190+ const topic = stringInterpolator . parse ( pubsubTopic , resolverData ) ;
191+ const ps = context ?. pubsub || opts ?. pubsub ;
192+ if ( isHivePubSub ( ps ) ) {
193+ return ps . subscribe ( topic ) [ Symbol . asyncIterator ] ( ) ;
194+ }
195+ return ps . asyncIterator ( topic ) [ Symbol . asyncIterator ] ( ) ;
196+ } ;
197+ if ( opts . filterBy ) {
198+ let filterFunction : any ;
199+ try {
200+ // eslint-disable-next-line no-new-func
201+ filterFunction = new Function ( 'root' , 'args' , 'context' , 'info' , `return ${ opts . filterBy } ;` ) ;
202+ } catch ( e ) {
203+ throw new Error (
204+ `Error while parsing filterBy expression "${ opts . filterBy } " in additional subscription resolver: ${ e . message } ` ,
205+ ) ;
206+ }
207+ subscribeFn = withFilter ( subscribeFn as any , filterFunction ) ;
208+ }
209+ const valuesFromResults = opts . result ? generateValuesFromResults ( opts . result ) : undefined ;
210+
211+ return {
212+ subscribe : subscribeFn ,
213+ resolve : ( payload : any , _ : any , ctx , info : GraphQLResolveInfo ) => {
214+ function resolvePayload ( payload : any ) {
215+ if ( valuesFromResults ) {
216+ return valuesFromResults ( payload ) ;
217+ }
218+ return payload ;
219+ }
220+ const stitchingInfo = info ?. schema . extensions ?. stitchingInfo as Maybe < StitchingInfo < any > > ;
221+ if ( ! stitchingInfo ) {
222+ return resolvePayload ( payload ) ; // no stitching, cannot be resolved anywhere else
223+ }
224+ const returnTypeName = getNamedType ( info . returnType ) . name ;
225+ const mergedTypeInfo = stitchingInfo . mergedTypes [ returnTypeName ] ;
226+ if ( ! mergedTypeInfo ) {
227+ return resolvePayload ( payload ) ; // this type is not merged or resolvable
228+ }
229+
230+ // we dont compare fragment definitions because they mean there are type-conditions
231+ // more advanced behavior. if we encounter such a case, the missing selection set
232+ // will have fields and we will perform a call to the subschema
233+ const requestedSelSet = info . fieldNodes [ 0 ] ?. selectionSet ;
234+ if ( ! requestedSelSet ) {
235+ return resolvePayload ( payload ) ; // should never happen, but hey 🤷♂️
236+ }
237+
238+ const availableSelSet = selectionSetOfData ( resolvePayload ( payload ) ) ;
239+ const missingSelectionSet = subtractSelectionSets ( requestedSelSet , availableSelSet ) ;
240+ if ( ! missingSelectionSet . selections . length ) {
241+ // all of the fields are already in the payload
242+ return resolvePayload ( payload ) ;
243+ }
244+
245+ // find the best subgraph by diffing the selection sets
246+ let subschema : Subschema | null = null ;
247+ let mergedTypeConfig : MergedTypeConfig | null = null ;
248+ for ( const [ requiredSubschema , requiredSelSet ] of mergedTypeInfo . selectionSets ) {
249+ const tentativeMergedTypeConfig = requiredSubschema . merge ?. [ returnTypeName ] ;
250+ if ( tentativeMergedTypeConfig ?. fields ) {
251+ // this resolver requires additional fields (think `@requires(fields: "x")`)
252+ // TODO: actually implement whether the payload already contains those fields
253+ // TODO: is there a better way for finding a match?
254+ continue ;
255+ }
256+ const diff = subtractSelectionSets ( requiredSelSet , availableSelSet ) ;
257+ if ( ! diff . selections . length ) {
258+ // all of the fields of the requesting (available) selection set is exist in the required selection set
259+ subschema = requiredSubschema ;
260+ mergedTypeConfig = tentativeMergedTypeConfig ;
261+ break ;
262+ }
263+ }
264+ if ( ! subschema || ! mergedTypeConfig ) {
265+ // the type cannot be resolved
266+ return resolvePayload ( payload ) ;
267+ }
268+
269+ return handleMaybePromise (
270+ ( ) => {
271+ if ( mergedTypeConfig . argsFromKeys ) {
272+ return batchDelegateToSchema ( {
273+ schema : subschema ,
274+ operation : 'query' as OperationTypeNode ,
275+ fieldName : mergedTypeConfig . fieldName ,
276+ returnType : new GraphQLList ( info . returnType ) ,
277+ key : mergedTypeConfig . key ?.( payload ) || payload , // TODO: should use valueFromResults on the args too?
278+ argsFromKeys : mergedTypeConfig . argsFromKeys ,
279+ valuesFromResults : mergedTypeConfig . valuesFromResults ,
280+ selectionSet : missingSelectionSet ,
281+ context : ctx ,
282+ info,
283+ dataLoaderOptions : mergedTypeConfig . dataLoaderOptions ,
284+ skipTypeMerging : false , // important to be false so that fields outside this subgraph can be resolved properly
285+ } ) ;
286+ }
287+ if ( mergedTypeConfig . args ) {
288+ return delegateToSchema ( {
289+ schema : subschema ,
290+ operation : 'query' as OperationTypeNode ,
291+ fieldName : mergedTypeConfig . fieldName ,
292+ returnType : info . returnType ,
293+ args : mergedTypeConfig . args ( payload ) , // TODO: should use valueFromResults on the args too?
294+ selectionSet : missingSelectionSet ,
295+ context : ctx ,
296+ info,
297+ skipTypeMerging : false , // important to be false so that fields outside this subgraph can be resolved properly
298+ } ) ;
299+ }
300+ // no way to delegate to anything, return empty - i.e. resolve just payload
301+ // should not happen though, there'll be something to use
302+ return { } ;
303+ } ,
304+ resolved => resolvePayload ( mergeDeep ( [ payload , resolved ] ) ) ,
305+ ) ;
306+ } ,
307+ } ;
308+ }
309+
174310export function resolveAdditionalResolversWithoutImport (
175311 additionalResolver :
176312 | YamlConfig . AdditionalStitchingResolverObject
@@ -179,143 +315,18 @@ export function resolveAdditionalResolversWithoutImport(
179315 pubsub ?: MeshPubSub | HivePubSub ,
180316) : IResolvers {
181317 const baseOptions : any = { } ;
182- if ( additionalResolver . result ) {
183- baseOptions . valuesFromResults = generateValuesFromResults ( additionalResolver . result ) ;
184- }
185318 if ( 'pubsubTopic' in additionalResolver ) {
186- const pubsubTopic = additionalResolver . pubsubTopic ;
187- let subscribeFn = function subscriber (
188- root : any ,
189- args : Record < string , any > ,
190- context : MeshContext ,
191- info : GraphQLResolveInfo ,
192- ) : MaybePromise < AsyncIterator < any > > {
193- const resolverData = { root, args, context, info, env : process . env } ;
194- const topic = stringInterpolator . parse ( pubsubTopic , resolverData ) ;
195- const ps = context ?. pubsub || pubsub ;
196- if ( isHivePubSub ( ps ) ) {
197- return ps . subscribe ( topic ) [ Symbol . asyncIterator ] ( ) ;
198- }
199- return ps . asyncIterator ( topic ) [ Symbol . asyncIterator ] ( ) ;
200- } ;
201- if ( additionalResolver . filterBy ) {
202- let filterFunction : any ;
203- try {
204- // eslint-disable-next-line no-new-func
205- filterFunction = new Function (
206- 'root' ,
207- 'args' ,
208- 'context' ,
209- 'info' ,
210- `return ${ additionalResolver . filterBy } ;` ,
211- ) ;
212- } catch ( e ) {
213- throw new Error (
214- `Error while parsing filterBy expression "${ additionalResolver . filterBy } " in additional subscription resolver: ${ e . message } ` ,
215- ) ;
216- }
217- subscribeFn = withFilter ( subscribeFn , filterFunction ) ;
218- }
319+ const { subscribe, resolve } = getResolverForPubSubOperation ( {
320+ pubsubTopic : additionalResolver . pubsubTopic ,
321+ pubsub,
322+ filterBy : additionalResolver . filterBy ,
323+ result : additionalResolver . result ,
324+ } ) ;
219325 return {
220326 [ additionalResolver . targetTypeName ] : {
221327 [ additionalResolver . targetFieldName ] : {
222- subscribe : subscribeFn ,
223- resolve : ( payload : any , _ , ctx , info ) => {
224- function resolvePayload ( payload : any ) {
225- if ( baseOptions . valuesFromResults ) {
226- return baseOptions . valuesFromResults ( payload ) ;
227- }
228- return payload ;
229- }
230- const stitchingInfo = info ?. schema . extensions ?. stitchingInfo as Maybe <
231- StitchingInfo < any >
232- > ;
233- if ( ! stitchingInfo ) {
234- return resolvePayload ( payload ) ; // no stitching, cannot be resolved anywhere else
235- }
236- const returnTypeName = getNamedType ( info . returnType ) . name ;
237- const mergedTypeInfo = stitchingInfo . mergedTypes [ returnTypeName ] ;
238- if ( ! mergedTypeInfo ) {
239- return resolvePayload ( payload ) ; // this type is not merged or resolvable
240- }
241-
242- // we dont compare fragment definitions because they mean there are type-conditions
243- // more advanced behavior. if we encounter such a case, the missing selection set
244- // will have fields and we will perform a call to the subschema
245- const requestedSelSet = info . fieldNodes [ 0 ] ?. selectionSet ;
246- if ( ! requestedSelSet ) {
247- return resolvePayload ( payload ) ; // should never happen, but hey 🤷♂️
248- }
249-
250- const availableSelSet = selectionSetOfData ( resolvePayload ( payload ) ) ;
251- const missingSelectionSet = subtractSelectionSets ( requestedSelSet , availableSelSet ) ;
252- if ( ! missingSelectionSet . selections . length ) {
253- // all of the fields are already in the payload
254- return resolvePayload ( payload ) ;
255- }
256-
257- // find the best subgraph by diffing the selection sets
258- let subschema : Subschema | null = null ;
259- let mergedTypeConfig : MergedTypeConfig | null = null ;
260- for ( const [ requiredSubschema , requiredSelSet ] of mergedTypeInfo . selectionSets ) {
261- const tentativeMergedTypeConfig = requiredSubschema . merge ?. [ returnTypeName ] ;
262- if ( tentativeMergedTypeConfig ?. fields ) {
263- // this resolver requires additional fields (think `@requires(fields: "x")`)
264- // TODO: actually implement whether the payload already contains those fields
265- // TODO: is there a better way for finding a match?
266- continue ;
267- }
268- const diff = subtractSelectionSets ( requiredSelSet , availableSelSet ) ;
269- if ( ! diff . selections . length ) {
270- // all of the fields of the requesting (available) selection set is exist in the required selection set
271- subschema = requiredSubschema ;
272- mergedTypeConfig = tentativeMergedTypeConfig ;
273- break ;
274- }
275- }
276- if ( ! subschema || ! mergedTypeConfig ) {
277- // the type cannot be resolved
278- return resolvePayload ( payload ) ;
279- }
280-
281- return handleMaybePromise (
282- ( ) => {
283- if ( mergedTypeConfig . argsFromKeys ) {
284- return batchDelegateToSchema ( {
285- schema : subschema ,
286- operation : 'query' as OperationTypeNode ,
287- fieldName : mergedTypeConfig . fieldName ,
288- returnType : new GraphQLList ( info . returnType ) ,
289- key : mergedTypeConfig . key ?.( payload ) || payload , // TODO: should use valueFromResults on the args too?
290- argsFromKeys : mergedTypeConfig . argsFromKeys ,
291- valuesFromResults : mergedTypeConfig . valuesFromResults ,
292- selectionSet : missingSelectionSet ,
293- context : ctx ,
294- info,
295- dataLoaderOptions : mergedTypeConfig . dataLoaderOptions ,
296- skipTypeMerging : false , // important to be false so that fields outside this subgraph can be resolved properly
297- } ) ;
298- }
299- if ( mergedTypeConfig . args ) {
300- return delegateToSchema ( {
301- schema : subschema ,
302- operation : 'query' as OperationTypeNode ,
303- fieldName : mergedTypeConfig . fieldName ,
304- returnType : info . returnType ,
305- args : mergedTypeConfig . args ( payload ) , // TODO: should use valueFromResults on the args too?
306- selectionSet : missingSelectionSet ,
307- context : ctx ,
308- info,
309- skipTypeMerging : false , // important to be false so that fields outside this subgraph can be resolved properly
310- } ) ;
311- }
312- // no way to delegate to anything, return empty - i.e. resolve just payload
313- // should not happen though, there'll be something to use
314- return { } ;
315- } ,
316- resolved => resolvePayload ( mergeDeep ( [ payload , resolved ] ) ) ,
317- ) ;
318- } ,
328+ subscribe,
329+ resolve,
319330 } ,
320331 } ,
321332 } ;
0 commit comments